Objectif
Pour mes développements, je cherchais à étendre les fonctionnalités des tableaux en PHP pour y intégrer :
- des contrôles d'intégrité afin que le contenu soit homogène, c'est à dire que chaque élément du tableau appartienne à la même classe d'objet
- des raccourcis d'utilisation pour les traitements en masse du contenu
J'en suis finalement arrivé à implémenter un nouveau conteneur PHP que j'ai appelé "Collection".
A la base d'un super-tableau.... un tableau !
Avant de construire un nouveau conteneur, il faut partir de la base à savoir un conteneur simple.
Comme il n'est pas possible en PHP d'hériter du type "Array", il faut que notre conteneur contienne un tableau.
Le résultat sera donc une sur-couche aux tableaux et non une véritable extension de ceux-ci.
class Collection
{
private $sClassName;
private $aoElt;
public function __construct( $sClassName = '')
{
$this->sClassName = $sClassName;
$this->aoElt = array();
}
}
L'attribut $sClassName représente la classe des futurs objets contenus dans notre conteneur.
Cet attribut peut être renseigné dès la construction de notre objet "Collection", à l'instar des templates en C++, ou il le sera lorsque l'on ajoutera le 1er objet à notre conteneur.
Enfin du contenu dans notre conteneur.
C'est dans cette méthode que nous allons remplir notre conteneur et nous assurer de l'homogénéité du contenu.
public function add( $oElt )
{
if( !is_object($oElt) )
{
throw new Exception('Invalid Parameter : [oElt] is not an object.');
}
else
{
if( empty($this->sClassName) )
{
$this->sClassName = get_class($oElt);
}
if( is_a( $oElt, $this->sClassName) )
{
$this->aoElt[] = $oElt;
}
else
{
throw new Exception('Invalid Parameter : parameter class mismatch with collection.');
}
return $this;
}
}
Le 1er contrôle va vérifier que notre collection ne contiendra que des objets.
Ensuite si la classe d'objet à contenir est inconnue, elle sera alors renseigné par la classe du 1er objet ajouté au conteneur.
Le 2ème contrôle va s'assurer que chaque objet à ajouter est de la classe stockée dans l'attribut $sClassName ou en hérite.
C'est ici que va être utile la définition de $sClassName au niveau du constructeur, si vous souhaitez avoir dans la collection plusieurs objets de classe différent mais héritant tous d'une même classe, il faut alors définir cette classe parent par le constructeur.
Pour finir, cette méthode retourne $this ce qui va permettre de chaîner les appels à la méthode add, à la manière de jQuery.
One Method to call them all
Maintenant, je voulais qu'un appel de méthode sur ma collection déclenche en cascade cet appel sur les objets contenus.
Comme la collection ne connait pas à l'avance les objets qu'elle va contenir et les méthodes que ceux-ci possèdent, j'ai utilisé la fonction magique __call qui permet d'intercepter tous les appels de méthode non défini au niveau d'un objet.
public function __call( $sMethod, $aParams )
{
if( 0 < count($this->aoElt) )
{
$oElt = $this->reset();
if( method_exists( $oElt, $sMethod ) )
{
foreach( $this->aoElt as $oElt )
{
call_user_func_array( array( $oElt,$sMethod), $aParams );
}
}
else
{
throw new Exception('Invalid call : the method ['.$sMethod.'] is not defined in class ['.$this->sClassName.'].');
}
}
}
La fonction va également contrôler que la méthode appellée existe au niveau des objets contenus sinon elle lancera une exception.
La fonction call_user_func_array est utilisé pour déclencher sur chaque instance d'objet l'appel de la méthode, contrairement à ce qu'indique une partie de la documentation PHP cette fonction réussit à appeler des méthodes non statiques.
Une touche de tableau dans notre collection
En l'état actuel, notre conteneur remplit déjà les 2 objectifs que je lui avais assigné mais pour qu'il devienne plus facilement manipulable, je vais introduire des comportements similaires qu'un conteneur classique de type "Array".
Tout dd'abord, je vais ajouter quelques méthodes que l'on attend à avoir sur un tableau :
public function reset()
{
return reset($this->aoElt);
}
public function next()
{
return next($this->aoElt);
}
public function prev()
{
return prev($this->aoElt);
}
public function current()
{
return current($this->aoElt);
}
public function end()
{
return end($this->aoElt);
}
public function each()
{
return each($this->aoElt);
}
Pour finir une touche d'itération, pour qu'il devienne utilisable avec un foreach :
class Collection implements Iterator
{
public function key()
{
return key($this->aoElt);
}
public function rewind()
{
$this->reset();
}
public function valid()
{
return ( false !== $this->current() );
}
}
Conclusion
Après toutes ces étapes, notre conteneur est enfin opérationnel et il remplit ses objectifs de départ
.
De nombreux contrôles sont effectués à chaque étape pour garantir l'homogénéité et la consistance des données contenues.
La source complète et une démonstration est disponible dans la section Ressource.
















$obj = $collection->next(); while( false !== $obj ) { //ton traitement.... $obj = $collection->next(); }ou tout simplement utiliser le foreach :foreach( $collection as $index => $objet ) { //ton traitement }La démonstration fournit dans la section ressource ne donne pas en effet un exemple d'utilisation avec le foreach, je devrais le rajouter