POO PHP5 : Créer un agrégateur à base de réflexion et de SPL
Date de publication : 04/05/2008
Par
Julien Pauli (Tutoriels, article et conférences PHP et developpement web) (Blog)
L'
agrégation est une
association UML qui lie deux classes. Alors que l'association indique qu'un objet utilise d'autres objets pour son fonctionnement, l'agrégation indique l'utilisation et le pilotage d'un ou plusieurs objets d'un même type.
La composition, elle, est une agrégation particulière dans laquelle un objet agrégé ne peut exister sans son objet conteneur.
La relation d'agrégation permet de répondre efficacement à l'étude des variations et des communalités d'une classe, en cherchant à déléguer des responsabilités à certains objets, agrégés dans d'autres (en général tout ce qui
est susceptible de varier).
Dans cet article, nous allons voir comment créer une classe mère qui permettra l'agrégation de ses filles, selon certaines règles.
I. Introduction au lien UML agrégation
II. Agregator : notre classe mère
II-A. Le UseCase (cas d'utilisation)
II-B. Design de la classe mère
II-C. SPL en action : itérateurs
III. Conclusion
I. Introduction au lien UML agrégation
Un objet agrège un autre lorsqu'il en contient une ou plusieurs instances, et que ces instances ne l'aident pas particulièrement dans une tâche précise (chacun sa responsabilité). Bien que ces 2 objets soient alors couplés, il peuvent très bien vivre aussi l'un sans l'autre.
Exemple : un ascenseur agrège des personnes : il peut agréger plusieurs personnes, il peut exister sans personnes (il est alors vide), et les personnes peuvent exister sans être dans l'ascenseur.
Au contraire, la composition est une agrégation particulière : elle est non partageable et non isolable.
Exemple : un hôtel se compose de chambre. Si l'hôtel est détruit, toutes les chambres le sont. Une chambre ne fait partie que d'un seul hôtel à la fois, elle n'est pas partageable, et n'est pas isolable : une chambre fait
obligatoirement partie d'un hôtel et ne peut exister sans.
En UML, une agrégation se matérialise par un petit losange blanc, une composition par un losange noir.
En PHP, il n'y a pas moyen de différencier, dans le code source, une agrégation d'une composition.

Agrégation

Composition
II. Agregator : notre classe mère
II-A. Le UseCase (cas d'utilisation)
Habituellement, lorsque vous voulez agréger un objet B dans un objet A, vous procédez comme suit :
- Créer dans la classe A une propriété, non publique en général, qui stockera des instances de B
- Créer un setter setB() accueillant des objets B, et un getter getB(), récupérant un objet B
- Vous avez fait une agrégation :-)
 |
Remarquons que les liens "association", et "agrégation", sont difficiles à cerner, les grands penseurs UML et conception définissent d'ailleurs vaguement l'agrégation.
Cependant le principe exposé ici se retrouve dans de nombreuses références bibliques, nous l'accepterons donc.
|
Nous allons tenter d'autommatiser ce processus. Si notre objet A doit agréger des B, mais aussi des C, des D, des E ...
Il va devenir pénible d'écrire toutes les méthodes et les propriétés. Dans la réalité, un tel cas ne se rencontre pas, ou très rarement. Le jeu n'en vaut donc pas la chandelle,
cet article n'aura donc pas de réelle application, si ce n'est de vous démontrer la grande fléxibilité du modèle objet de PHP.
Ainsi notre classe A va hériter d'Agregator, dans laquelle il y aura toute la logique de contrôle des agrégations.
Les spécifications sont les suivantes :
- Tout objet héritant d'Agregator doit pouvoir utiliser une méthode setSomething().
- Something doit représenter une propriété non publique de la classe.
- Il ne peut être affecté à la propriété something qu'un objet instance de la classe Something (qu'il faudra évidemment définir)
| Cas d'utilisation |
<?php
class Agregateur extends Agregator
{
protected $foo;
protected $bar;
public $anaska;
}
class Foo { }
class Bar { }
$agregateur = new Agregateur();
$agregateur->setFoo(new Foo);
$agregateur->setBar(new Bar);
$agregateur->setAnaska(new Bar);
$agregateur->setAnything(new AnyThing)
$agregateur->setBar('hello world')
|
Nous ajouterons ensuite une particularité, qui est en fait assez commune : rendre l'agregateur itérable, afin qu'il nous "rende" tous ce qu'il contient :
| itération de l'agrégateur |
<?php
foreach ($agregateur as $objetagrege) {
var_dump($objetagrege);
}
|

agregator
II-B. Design de la classe mère
Pour arriver à un tel résultat, vous vous doutez bien que la classe mère,
Agregator, comporte un bout de code. Ce code est intéréssant pour tous ceux qui veulent apprendre
un peu plus sur la
réflexion, et sur la
SPL
| Agregator.php |
<?php
abstract class Agregator
{
private $props = array();
final private function __call($fun,$args)
{
if (substr($fun, 0, 3) == "set" &&
array_key_exists(0,$args)
) {
$class = new ReflectionClass($this);
try {
$param = new ReflectionProperty($this,$paramName = strtolower(substr($fun,3)));
}catch(ReflectionException $e) {
trigger_error("Call to undefined method ".get_class($this)."::$fun()",E_USER_ERROR);
}
if (!$param->isPublic() &&
$args[0] instanceof $paramName) {
if (!is_array($this->$paramName)) {
$this->$paramName = array();
}
array_push($this->$paramName,$args[0]);
if(!in_array($paramName,$this->props)) {
$this->props[] = $paramName;
}
}
}else{
trigger_error("Call to undefined method ".get_class($this)."::$fun()",E_USER_ERROR);
}
return $this;
}
}
|
Nous définissons une méthode __call() qui va intercepter les méthodes setSomething(). Nous la déclarons final de manière à ce qu'elle ne puisse être redéfinie.
En effet l'enfant peut changer tout le comportement s'il redéfinit __call(), en oubliant d'appeler le parent par exemple.
Puis l'API de réflexion entre en jeu. Cette API introduite en même temps que le modèle objet de PHP5, permet d'introspecter dans des classes et des objets pour fouiller.
Ici, nous analysons $this, qui sera résolut en l'objet qui va hériter d'Agregator, puis nous vérifions qu'il possède une propriété non publique du même nom que celui de la méthode, privée de la partie 'set',
et passée en minuscule.
Dans tous les cas, nous ne préférons pas lever d'exception, afin de garantir le comportement par défaut de PHP (via les erreurs).
Remarquez que nous gérons des collections d'instances. Nous pouvons appeler plusieurs fois le même 'setter', celui-ci va stocker toutes les instances des objets agrégés dans un tableau.
Notre méthode __call() retourne $this, donc nous pouvons créer une interface fluide et ainsi écrire $agregateur->setA(new A)->setB(new B)->setC(new C).....
II-C. SPL en action : itérateurs
Très souvent lorsqu'il y a agrégation, il y a itération. Pourquoi donc ne pas rajouter un itérateur à notre classe mère agregator ? C'est très simple en plus : il suffit d'itérer sur toutes les propriétés non publiques de l'objet,
et si celles-ci ont été récemment affectées, il faut retourner les instances des objets agrégés.
Heureusement, la SPL met à disposition RecursiveArrayIterator, et l'interface Iteratoragregate nous sera bien utile :
| suite de agregator.php |
<?php
abstract class Agregator implements Iteratoragregate
{
final public function getIterator()
{
$array = array();
$self = new ReflectionObject($this);
foreach ($self->getProperties() AS $property) {
$array[] = $property->getName();
}
$props = array_intersect($this->props,$array);
$array = array();
foreach ($props as $prop) {
$array[] = $this->$prop;
}
return new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
}
|
Suite à cela, il est possible d'itérer sur notre classe principale, et elle nous donnera directement tous les objets y étant agrégés :
| Utilisation de l'itérateur |
<?php
class Agregateur extends Agregator
{
protected $foo;
protected $bar;
protected $foobar;
}
class Foo { }
class Bar { }
class Foobar { }
$ag = new Agregateur();
$ag->setFoo(new Foo);
$ag->setBar(new Bar);
$ag->setBar(new Bar);
$ag->setFoobar(new Foobar);
foreach ($ag as $object) {
var_dump($object);
}
|
 |
Un bug dans RecursiveArrayIterator empêche l'itérateur de retourner des objets.
Cette partie du code (l'itération des objets agrégés) ne fonctionne qu'à partir de PHP5.3
|
III. Conclusion
A la question "à quoi cet exemple va-t-il me servir concrêtement ?", je réponds : "à rien". En PHP, l'héritage multiple n'existe pas, ainsi si vous héritez d'
Agregator, vous ne pourrez plus hériter
d'autre chose. De la même manière : vous ne pourrez plus définir de méthode
__call() dans vos enfants car elle est déclarée
final. C'est fait exprès pour obliger l'enfant à implémenter ce processus.
Quoiqu'il en soit, le but était de montrer que l'API de réflexion est très pratique : elle permet d'introspecter dans des classes, des objets, des fonctions, des paramètres afin de les analyser et de changer un comportement à la volée.
Aussi, très souvent lorsqu'un objet en agrège un autre (ou plusieurs), on a recours à un itérateur. Dans
ZendFramework par exemple, c'est le cas.


Copyright © 2007 . Aucune reproduction, même partielle, ne peut être faite
de ce site et de l'ensemble de son contenu : textes, documents, images, etc
sans l'autorisation expresse de l'auteur.
Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
Cette page est déposée à la
SACD.