PHP Objet

PHP objet : chapitre 2

Poursuivons notre exploration des capacités « objet » de PHP.

Chargement automatique des classes

Lorsqu’une application atteint un certain volume elle dispose de nombreuses classes dispersées dans de nombreux fichiers. Il est évidemment possible de charger un fichier avec les méthodes require et include, mais ça devient rapidement laborieux. Avec la version 5 de PHP est apparue la possibilité de faire du chargement automatique de classe. Voici un exemple élémentaire. Créez un fichier avec ce code (je rappelle que je ne mentionne plus le tag de PHP) :

$mon_volume = new Volume();

Si vous lancez ce fichier il est évident que vous allez avoir une erreur PHP parce qu’on ne connaît pas la classe Volume. En codeur bien organisé vous créez un autre fichier nommé Volume avec cette classe :

class Volume {}

Et vous l’incluez dans le code pour qu’elle soit prise en compte :

include 'Volume.php';
$mon_volume = new Volume();

Maintenant tout fonctionne correctement. Voyons maintenant comment faire avec le chargement automatique :

function __autoload($class_name) {
    include $class_name.'.php';
}
$mon_volume = new Volume();

On utilise la méthode magique __autoload qui est appelée dès qu’une classe (ou une interface, mais on n’en a pas encore parlé) non connue est utilisée. On voit qu’on inclue le fichier de la classe concernée en considérant qu’il a le même nom que la classe, ce qui est une bonne pratique. Maintenant que se passe-t-il si le fichier n’existe pas ? Nous allons évidemment rencontrer encore une erreur PHP. Depuis la version 5.3 il est possible d’intercepter cette erreur :

function __autoload($class_name) {
    echo "On essaie de charger la classe $class_name".'<br>';
    throw new Exception("Impossible de charger la classe $class_name.");
}

try {
    $mon_volume = new Volumes();
} catch (Exception $e) {
    echo $e->getMessage(), "\n";
}
On essaie de charger la classe Volumes
Impossible de charger la classe Volumes.

Mais ce n’est pas le genre d’exception qui me viendrait à l’idée d’intercepter parce qu’a priori mon fichier existe et je ne pense pas qu’il va disparaître Surprised. Le seul moment où l’exception pourrait apparaître est pendant la phase de développement et il n’est alors pas gênant de recevoir la description de l’erreur dans le navigateur, c’est même fondamental.

Utilisation de la bibliothèque SPL

Avec la version 5.3 de PHP est apparue la bibliothèque SPL (Standard PHP Library). Ce qui fait dire que le modèle objet de PHP n’est pas vide mais possède déjà des classes et des interfaces. Dans cette bibliothèque nous allons nous intéresser à la méthode spl_autoload_register. Voici une nouvelle version de l’exemple précédent :

function classLoader($class_name)
{
    include $class_name.'.php';
}
spl_autoload_register('classLoader');
$mon_volume = new Volume;

La méthode __autoload ne peut être utilisée qu’une fois, par contre la méthode spl_autoload_register permet d’empiler plusieurs appels de fonctions comme nous allons bientôt le voir. Parce que vous pourriez vous demander ce qu’on a gagné avec ce nouveau code Undecided. Imaginez que vous avez deux dossiers, l’un contenant vos librairies de code (dans un dossier librairies) et l’autre vos classes personnalisées (dans un dossier classes). Vous désirez charger automatiquement toutes ces classes. Avec ce type de code vous résolvez facilement ce chargement automatique :

function classLoader($class_name)
{
    $file = 'classes/'.$class_name.'.php';
    if (file_exists($file)) {
        include $file;
    }
    return false;
}
function libLoader($class_name)
{
    $file = 'librairies/'.$class_name.'.php';
    if (file_exists($file)) {
        include $file;
    }
    return false;
}
spl_autoload_register('classLoader');
spl_autoload_register('libLoader');
// chargement d'une classe
$mon_volume = new Volume;
// chargement d'une librairie
$mon_volume = new Calcul;

Vous remarquez que cette fois on fait deux fois appel à la méthode spl_autoload_register. On prend aussi la précaution de tester la validité du fichier pour éviter de recevoir une exception.

Les classes abstraites

Une classe abstraite est une classe qui ne permet pas d’instancier un objet. Mais alors à quoi ça sert ? Surprised. Si on ne peut pas instancier un objet avec une telle classe par contre on peut créer une classe fille qui en hérite et c’est là que ça devient intéressant parce que la classe abstraite peut imposer à la classe fille de définir des méthodes. Voyons un exemple :

abstract class Vehicule
{
	abstract protected function getSupport();
	protected $nom;
	public function getNom() {
		echo $this->nom;
	}
}
class Voiture extends Vehicule
{
	public function __construct($nom) {
		$this->nom = $nom;
	}
	protected function getSupport() {
		return 'route';
	}
	public function afficheSupport() {
		return $this->getSupport();
	}
}
$ma_voiture = new Voiture('Titine');
echo "{$ma_voiture->getNom()} roule sur une {$ma_voiture->afficheSupport()}";
Titine roule sur une route

J’ai déclaré une classe abstraite Vehicule. Si vous tentez de créer un objet directement à partir de cette classe vous obtiendrez une exception :

Fatal error: Cannot instantiate abstract class Vehicule

J’ai aussi déclaré dans cette classe une méthode abstraite getSupport(), que je dois obligatoirement prévoir dans les classes filles sinon on a droit aussi à une exception :

Fatal error: Class Voiture contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Vehicule::getSupport)

D’autre part la méthode dans la classe fille doit avoir au moins la même visibilité, donc dans notre cas protected ou public. Si on la déclare private là aussi on reçoit une belle exception :

Fatal error: Access level to Voiture::getSupport() must be protected (as in class Vehicule) or weaker

Dernière contrainte : la signature (nombre et type de paramètres) doit être identique sinon, et bien encore PHP qui se plaint :

Fatal error: Declaration of Voiture::getSupport() must be compatible with that of Vehicule::getSupport()

Les interfaces

Une interface est destinée à indiquer quelles méthodes une classe doit implémenter. Mais contrairement à une classe abstraite on ne peut avoir aucun code fonctionnel mais juste des définitions. Reprenons l’exemple précédent avec une interface :

interface VehiculeInterface
{
	public function getSupport();
}
class Voiture implements VehiculeInterface
{
	protected $nom;	
	public function __construct($nom) {
		$this->nom = $nom;
	}
	public function getNom() {
		echo $this->nom;
	}
	public function getSupport() {
		return 'route';
	}
}
$ma_voiture = new Voiture('Titine');
echo "{$ma_voiture->getNom()} roule sur une {$ma_voiture->getSupport()}";

L’interface VehiculeInterface prévoit une méthode publique getSupport() que doit obligatoirement comporter la classe qui implémente cette interface. Sinon encore une exception :

Class Voiture contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (VehiculeInterface::getSupport)

L’intérêt des interface est de présenter un accès normalisé à une classe. Puisque nous somme dans un blog concernant Laravel prenons un exemple d’interface dans la bibliothèque Illuminate :

<?php namespace Illuminate\Auth;

interface UserInterface {

	/**
	 * Get the unique identifier for the user.
	 *
	 * @return mixed
	 */
	public function getAuthIdentifier();

	/**
	 * Get the password for the user.
	 *
	 * @return string
	 */
	public function getAuthPassword();

}

On trouve une interface bien rangée dans l’espace de nom Illuminate\Auth. Cette interface comporte deux définitions de méthodes, une pour le retour de l’identifiant de l’utilisateur et une autre pour son mot de passe. Cette interface est implémentée par la classe GenericUser :

<?php namespace Illuminate\Auth;

class GenericUser implements UserInterface {

	/**
	 * All of the user's attributes.
	 *
	 * @var array
	 */
	protected $attributes;

	/**
	 * Create a new generic User object.
	 *
	 * @param  array  $attributes
	 * @return void
	 */
	public function __construct(array $attributes)
	{
		$this->attributes = $attributes;
	}

	/**
	 * Get the unique identifier for the user.
	 *
	 * @return mixed
	 */
	public function getAuthIdentifier()
	{
		return $this->attributes['id'];
	}

	/**
	 * Get the password for the user.
	 *
	 * @return string
	 */
	public function getAuthPassword()
	{
		return $this->attributes['password'];
	}

	/**
	 * Dynamically access the user's attributes.
	 *
	 * @param  string  $key
	 * @return mixed
	 */
	public function __get($key)
	{
		return $this->attributes[$key];
	}

	/**
	 * Dynamically set an attribute on the user.
	 *
	 * @param  string  $key
	 * @param  mied    $value
	 * @return void
	 */
	public function __set($key, $value)
	{
		$this->attributes[$key] = $value;
	}

	/**
	 * Dynamically check if a value is set on the user.
	 *
	 * @return bool
	 */
	public function __isset($key)
	{
		return isset($this->attributes[$key]);
	}

	/**
	 * Dynamically unset a value on the user.
	 *
	 * @return bool
	 */
	public function __unset($key)
	{
		unset($this->attributes[$key]);
	}

}

Et vous retrouvez bien dans cette classe les deux méthodes prévues par l’interface. Remarquez au passage l’utilisation des méthodes magiques que nous avons déjà vues __set et __get pour la gestion des attributs. Vous trouvez deux autres méthodes magiques __isset et __unset. La première est appelée lorsqu’on essaie d’accéder avec isset() ou empty() à une propriété inaccessible et la seconde avec unset(). On en arrive ainsi à une classe avec un code très épuré et parfaitement fonctionnelle. Le fait de l’implémenter à partir d’une interface permet de facilement connaître son fonctionnement juste en regardant cette interface.

Les espaces de noms

Lorsqu’on a une application importante on risque de se trouver avec des conflits dans les noms de classes ou fonctions. De même lorsqu’on utilise des librairies tierces. Il est alors opportun d’utiliser les espaces de noms. Un espace de nom permet de regrouper et d’isoler du code : classes, déclarations, fonctions… ce qui permet d’éviter des conflits avec des noms identiques. Prenons un premier exemple :

namespace Espace1 {
	class maClasse
	{
		public function __construct()	
		{
			echo 'Création classe dans espace de nom Espace1';
		}
	}
}
namespace Espace2 {
	class maClasse
	{
		public function __construct()	
		{
			echo 'Création classe dans espace de nom Espace2';
		}		
	}
	$objet = new maClasse;
}

Ici on a deux espaces de noms Espace1 et Espace2 avec un classe du même nom maClasse. Si on exécute ce code on obtient :

Création classe dans espace de nom Espace2

On se rend compte que les deux espaces n’interfèrent pas. Maintenant comment utiliser à partir d’un espace une classe d’un autre espace ? Il suffit de référencer cet espace :

namespace Espace1 {
	class maClasse
	{
		public function __construct()	
		{
			echo 'Création classe dans espace de nom Espace1';
		}
	}
}
namespace Espace2 {
	class maClasse
	{
		public function __construct()	
		{
			echo 'Création classe dans espace de nom Espace2';
		}		
	}
	$objet = new \Espace1\maClasse;
}
Création classe dans espace de nom Espace1

Remarquez bien la syntaxe ! On peut aussi importer un espace de nom :

namespace Espace1 {
	class maClasse
	{
		public function __construct()	
		{
			echo 'Création classe dans espace de nom Espace1';
		}
	}
}
namespace Espace2 {
	use Espace1;
	$objet = new Espace1\maClasse;
}
Création classe dans espace de nom Espace1

On peut aussi juste importer une classe :

namespace Espace1 {
	class maClasse
	{
		public function __construct()	
		{
			echo 'Création classe dans espace de nom Espace1';
		}
	}
}
namespace Espace2 {
	use Espace1\maClasse;
	$objet = new maClasse;
        echo '<br>','Je suis dans l\'espace ',__NAMESPACE__;
}
Création classe dans espace de nom Espace1
Je suis dans l'espace Espace2

J’ai aussi utilisé la constante __NAMESPACE__ qui permet de savoir dans quel espace de nom on se situe.

Un convention existe pour le nommage de ces espaces, c’est le PSR-0 dont je vous ai déjà parlé. Cette convention est utile principalement si vous voulez exposer votre code pour d’autres utilisateurs. Et évidemment vous devez vous référer à la documentation pour tous les détails sur ces espaces de noms.

Print Friendly, PDF & Email

Laisser un commentaire