POO PHP5 : Standard Php Library (SPL)

Image non disponible


précédentsommairesuivant

II. Dans le coeur de la SPL

La SPL comporte ainsi des classes et des interfaces natives à PHP.
Le plus grand atout qu'offre la SPL est l'itérateur, et il y en a de toutes sortes. Rappelons d'abord ce qu'est un itérateur : C'est un objet qui permet de parcourir des éléments contenus dans un autre objet rendu itératif, le plus souvent il s'agit d'un conteneur (tableau, objet, jeu de résultats, arbre, liste ...). Le conteneur doit alors fournir des méthodes à l'itérateur (en implémentant une interface) afin de lui permettre de le parcourir : il devient alors itératif.
On peut assimiler les itérateurs à des curseurs, dans le cas des bases de données.
En réalité, PHP utilise lui-même les itérateurs, à chaque fois que vous faites un foreach(), ou un count().
La syntaxe PHP du foreach est :

 
Sélectionnez
<?php
foreach ($foo as $key=>$val)
{
    echo "$key vaut $val";
}

En interne, il se passe (grossièrement) ceci :

 
Sélectionnez
<?php
$fooIt = $foo->getIterator();
$fooIt->rewind();
while ($fooIt->valid()) {
    echo "$fooIt->key() vaut $fooIt->current()";
    $fooIt->next();
}
Image non disponible

Pour un type PHP array, nous avons à disposition des fonctions d'itération : next(), prev(), key(), current()... qui en interne vont utiliser les processus d'itération. foreach fonctionne sur les objets depuis PHP5, car ils implémentent tous, en interne, l'interface Traversable, ce qui signifie qu'ils sont parcourables au moyen d'une structure foreach.
Si j'utilise un foreach sur un objet, je vais avoir la liste de ses attributs publics, et de leurs valeurs, respectivement placés en clé et valeur :

 
Sélectionnez
<?php
class Bar
{
    private $_var = 2&#160;;
    public $foo = 8;
}

$bar = new Bar;
foreach ($bar as $k => $v) {
    echo $k . " a la valeur ". $v; 
}

// affiche foo a la valeur 8

Ceci est le comportement par défaut de PHP, l'itérateur récupère les noms des proriétés publiques en clé, et leur valeur en valeur.

II-A. Iterator

Il est possible de modifier ce comportement par défaut, en implémentant l'interface Iterator; rappelez-vous de la syntaxe avec le while situé plus haut :

 
Sélectionnez
<?php
class MonArray implements Iterator
{
    private $_tab = array();

    private $_pas;

    public function __construct(array $array, $pas = 1)
    {
        $this->_tab = $array;
        $this->_pas = abs((int)$pas);
    }

    public function valid()
    {
        return array_key_exists(key($this->_tab), $this->_tab);
    }

    public function next()
    {
        for ($i=1; $i<=$this->_pas; $i++) {
            next($this->_tab);
        }
        return $this;
    }

    public function rewind()
    {
        reset($this->_tab);
        return $this;
    }

    public function key()
    {
        return key($this->_tab);
    }

    public function current()
    {
        return current($this->_tab);
    }
}

J'ai crée une classe "par dessus" un tableau, et je vais pouvoir spécifier le pas d'avancée de l'itération, dans le constructeur. (Notez qu'il arrive que je retourne $this, sur des méthodes ne demandant pas obligatoirement un return, ceci permet un chainage des méthodes à l'utilisation)

 
Sélectionnez
<?php
$tablo = range (0, 10);
$mon_array = new MonArray($tablo, 2));

foreach($mon_array as $v)
{
    echo $v;
}

Ce script m'affiche une suite de chiffres pairs 0,2,4,6,8,10.
J'ai donc modifié le comportement de foreach, en lui demandant d'itérer les éléments avec un pas que je donne au constructeur de mon objet, dans mon exemple.

Très important : Utiliser l'interface Iterator permet de décaler la logique de selection et de navigation dans les données. L'interface Iterator permet au développeur de prendre la main sur le comportement de la structure foreach, lorsqu'appliquée à un objet. Le développeur doit donc guider foreach au moyen de 5 méthodes.
Grâce à Iterator, dans le code d'une boucle foreach je n'ai plus aucune logique de sélection des données, le foreach me retourne directement toutes les données auxquelles je m'attends.
Vous devez donc faire attention au code dans ces 5 méthodes, par exemple si valid() retourne 'true' tout le temps, foreach est alors dans une boucle infinie.

Pour résumer : avec Iterator, c'est au développeur de guider tout le comportement d'un futur foreach appliqué sur son objet. L'objet en question gagne alors en puissance puisqu'il est capable lui-même de décrire la manière dont il veut qu'on le parcourt.

II-B. IteratorIterator

Image non disponible
IteratorIterator

IteratorIterator est un itérateur générique simple permettant d'itérer sur un objet implémentant Traversable, et donc à fortiori Iterator, qui en hérite. foreach réagit à tout ce qui est Traversable, et certains objets internes à PHP comme PDOStatement implémentent cette interface. On peut donc itérer dessus avec un simple foreach, mais si on doit utiliser un itérateur de plus haut niveau (comme ceux dans cet article), il sera necéssaire de tranformer la structure traversable, en une structure itérative. IteratorIterator existe dans ce but.
La différence entre Traversable et Iterator est difficile à saisir, elle se situe en interne dans le ZendEngine.

Il n'est pas possible d'un point de vue utilisateur, d'implémenter l'interface Traversable directement, vous devez utiliser au minimum Iterator

 
Sélectionnez
<?php
$pdo  = new PDO('mysql:host=localhost;dbname=test','logintest','passtest');
$stmt = $pdo->query("SELECT * FROM test");
$it   = new IteratorIterator($stmt);

foreach ($it as $val) {
    echo $val;
}

Ce code affiche tous les résultats de la requête SQL concernée, mais l'objet IteratorIterator n'est pas nécessaire ici. Voyez la section concernant CachingIterator pour un exemple plus concrêt.

II-C. IteratorAggregate

Image non disponible

IteratorAggregate est très semblable à Iterator, mais comme son nom l'indique, elle va vous proposer de définir une méthode permettant de retourner un itérateur plutôt que d'embarquer ses méthodes dans votre classe. En effet, Iterator est très utile mais "pollue" souvent les méthodes d'une classe avec ces cinq méthodes. IteratorAggregate vous propose de définir une seule méthode, getIterator(), qui devra obligatoirement retourner un itérateur qu'utilisera foreach.

 
Sélectionnez
<?php
class Voiture implements IteratorAggregate
{
    protected $_personnes = array();
    
    public function ajouter(Personne $p)
    {
        $this->_personnes[] = $p;
    }
    
    public function getIterator()
    {
        return new ArrayIterator($this->_personnes);
    }
}

Dans l'exemple ci-dessus, on peut ajouter des objets Personnes dans un objet Voiture. Celui-ci stocke les personnes en interne dans un tableau. Lorsqu'on itère sur l'objet Voiture, on voudrait voir ressortir toutes les personnes. Il suffit alors d'implémenter IteratorAggregate et de retourner un itérateur sur la structure des personnes (un tableau), à savoir un ArrayIterator.
foreach va être suffisament intelligente pour appeler la méthode getIterator() dès le début, puis itérer sur l'objet retourné en lieu et place de l'objet actuel.

"IteratorAggregate": une agrégation d'un itérateur. Voyez Les liaisons UML implémentées avec PHP pour plus d'informations sur la liaison UML agrégation, même si celle de IteratorAggregate est un peu particulière.

II-D. Countable

Image non disponible

Vous pouvez de même implémenter Countable, seule la méthode count() devra être définie, et elle va guider le comportement de la fonction count() de PHP :

 
Sélectionnez
<?php
class MonArray implements Iterator, Countable
{
    // la classe reste la même qu'au dessus, on rajoute cependant&#160;:
    public function count()
    {
        return count($this->_tab);
    }
}

Je retourne naturellement le nombre de valeurs dans mon tableau, mais par exemple pour un conteneur représentant un jeu de résultats de base de données, le calcul aurait pu être différent.

A retenir : Countable modifie le comportement par défaut de la fonction count() du langage PHP, de la même manière que Iterator modifie le comportement de la structure foreach() de PHP

II-E. ArrayAccess

Image non disponible

L'interface ArrayAccess modifie carrément la manière dont se comportent les "[" et "]" pour accéder aux tableaux dans le langage PHP. Un objet peut alors être utilisé quasiment comme un tableau :

 
Sélectionnez
<?php

class FunctionProxy implements ArrayAccess
{
    const SEPARATOR = '_';    
    private $_func;
    
    public function offsetGet($offset)
    {
        $this->_func = $offset;
        return $this;
    }
    
    public function offsetSet($offset, $value)
    {
        throw new InvalidArgumentException('Affectation interdite');
    }
    
    public function offsetExists($offset)
    {
        return ($this->_func == $offset);
    }
    
    public function offsetUnset($offset)
    {
        throw new InvalidArgumentException('Déréférencement interdit');
    }
    
    public function __call($func, $args)
    {
        $func = $this->_func.self::SEPARATOR.$func;
        if (!function_exists($func)) {
            throw new BadMethodCallException("PHP function $func doesn't exist");
        }        
        return @call_user_func_array($func, $args);
    }
}
$p = new functionProxy();
$p['array']->combine($array1, $array2); // proxy vers la fonction PHP array_combine()
$p['str']->replace(/* ... */); // proxy vers la fonction PHP str_replace()

Si vous connaissez SimpleXML, vous noterez que vous pouvez accéder, via l'objet SimpleXMLElement, aux propriétés des tags xml, avec une syntaxe de tableau. Ca n'est pas magique ! , c'est que tout simplement SimpleXMLElement implémente ArrayAccess (en interne).
Ce comportement (comme tous les autres d'ailleurs) est très utile dans l'élaboration de frameworks.

II-F. RecursiveIterator

Image non disponible

Grâce à l'interface RecursiveIterator , je vais implémenter des méthodes qui vont permettre à l'itérateur d'itérer sur des éléments, de même nature, nichés les uns dans les autres. Le cas bateau représente un tableau, dans un tableau, dans un tableau ...
Il est très facile d'itérer toutes les valeurs d'un coup, il suffit d'implémenter l'interface RecursiveIterator, et d'utiliser l'itérateur RecursiveIteratorIterator adapté avec :

 
Sélectionnez
<?php
class MonArray implements RecursiveIterator
{
    private $_tab = array();
    private $_pas;

    public function __construct(array $array, $pas = 1)
    {
        $this->_tab = $array;
        $this->_pas = abs((int)$pas);
    }

    public function valid()
    {
        return array_key_exists(key($this->_tab), $this->_tab);
    }

    public function next()
    {
        for ($i=1; $i<=$this->_pas; $i++) {
            next($this->_tab);
        }
        return $this;
    }

    public function rewind()
    {
        reset($this->_tab);
        return $this;
    }

    public function key()
    {
        return key($this->_tab);
    }

    public function current()
    {
        return current($this->_tab);
    }

    public function hasChildren()
    {
        return is_array(current($this->_tab));
    }

    public function getChildren()
    {
        return new self(current($this->_tab));
    }

L'exemple :

 
Sélectionnez
<?php
$tablo = range (0, 10);
$tablo[] = array('u', array('w'));
$tablo[] = array('x', array('z'));
$a = new RecursiveIteratorIterator(new MonArray($tablo, 1));

foreach($a as $k => $v) {
    echo $v.' ';
}

Affiche : 0 1 2 3 4 5 6 7 8 9 10 u w x z
RecursiveIteratorIterator est un itérateur, et il attend obligatoirement un paramètre implémentant l'interface RecursiveIterator. C'est le cas de MonArray. Il définit 2 nouvelles méthodes (par rapport à Iterator) qui vont être appelées par RecursiveIteratorIterator.
La première, hasChildren(), permet de savoir si le contenu en cours d'itération peut lui-même être itéré de la même manière que son père actuel. Savoir si le père possède un enfant.
On retourne donc un booléen qui dans notre cas regarde si l'élément couramment itéré est un autre tableau.
Si true est retourné, alors RecursiveIteratorIterator appelle immédiatement getChildren(), qui doit lui retourner un objet de la même classe que l'objet courament itéré (un enfant) à son tour, et il commence l'itération dessus.
Lorsqu'il a fini avec un enfant, il reprend le cours d'itération de son père, et ainsi de suite, quelle que soit la profondeur.
Nul besoin d'écrire des foreach dans des foreach..., la récursivité est assurée en interne par l'interface.
Vous pouvez créer de la même manière un menu de type 'fil conducteur' , contenant des élements de menus, ou des menus, etc. De manière plus générale, tout élément itératif composite, c'est à dire pouvant être parcouru et pouvant contenir des instances de lui-même, est susceptible d'implémenter RecursiveIterator.
On pense immédiatement au cas du dossier, comportant des dossiers, des fichiers, les dossiers étant des sortes de fichiers, etc.

II-G. LimitIterator

Image non disponible

Cet itérateur est tout aussi simple, il prend en paramètre un objet itératif (implémentant Iterator), puis une valeur de départ et un nombre de valeurs à parcourir.
Il limite alors l'objet itératif à certains résultats, tout bêtement, mais cela peut s'avérer plus que pratique :

 
Sélectionnez
<?php
$itere = new RecursiveIteratorIterator(new MonArray($tablo, 1));

$limiteur = new LimitIterator($itere, 5, 4);
foreach($limiteur as $v) {
    echo $v;
}

Je vais afficher 8 9 10 u. Je pars du 5ème élément, et j'en affiche 4.
Il est à ce titre impressionnant de remarquer combien les gens essayent de réinventer la roue, en mettant des "if(...) return" ou je ne sais quoi dans leur boucle foreach, la rendant rapidement illisible.

II-H. SeekableIterator

Image non disponible
SeekableIterator

SeekableIterator est un itérateur, mais dans lequel on peut se déplacer pour demander l'accès à l'enregistrement se trouvant à la position X de l'objet itéré.

 
Sélectionnez
<?php
class MonArray implements SeekableIterator
{
    // même code qu'auparavent
    
    public function seek($index)
    {
        if (array_key_exists($index, $this->_tab)) {
            $this->_key = $index;
            return $this->_tab[$index];
        }
    }
}

On voit dans ce code que l'on peut déplacer le curseur de l'itérateur à la place que l'on souhaite.

II-I. FilterIterator

Image non disponible

Même principe que LimitIterator, FilterIterator ne va retourner un résultat que si celui-ci est accepté, via une règle, que vous devez définir grâce à la méthode accept()
FilterIterator est une classe abstraite qui définit une méthode abstraite accept(). C'est un proxy qui agrège un itérateur, et va lui faire suivre les demandes.

 
Sélectionnez
<?php 
class MonArrayFiltre extends FilterIterator 
{
    public function __construct(MonArray $mon_array)
    {
        parent::__construct(new RecursiveIteratorIterator($mon_array));
    }
	
    public function accept()
    {
        $it = $this->getInnerIterator();
        return strlen($it->current()) == 1 ? true: false;
    }
}

$a = new MonArrayFiltre(new MonArray(array('erf', array('r'), 'x', 'y', 'zz')));

foreach($a as $v) {
    echo $v;
}

Ce petit code va afficher 'rxy'. Je lui ai demandé de n'afficher que les éléments de MonArray d'une seule lettre.
FilterIterator attend en paramètre un objet implémentant Iterator, c'est le cas de MonArray que nous avons déja vu plus haut.

II-J. RegexIterator

Image non disponible
RegexIterator

RegexIterator hérite de FilterIterator, et permet d'utiliser la méthode accept() avec une expression régulière.

 
Sélectionnez
<?php
class DefinedFunction extends RegexIterator 
{
    public function __construct($regex, $mode = 0, $flags = 0, $preg_flags = 0)
    {
        $phpFunctions = get_defined_functions();
        parent::__construct(new ArrayIterator(array_shift($phpFunctions)), $regex, $mode, $flags, $preg_flags);
    }
}

foreach (new DefinedFunction('/str/') as $key => $val) {
    echo $val;
}

Cet exemple affiche toutes les fonctions PHP qui comprennent la chaine 'str' dedans. RegexIterator permet de définir certains paramètres pour piloter l'expression régulière. $mode par exemple, peut contenir RegexIterator::GET_MATCH, qui va alors retourner le tableau $matches utilisé par preg_match() en dessous, ou encore RegexIterator::SPLIT, qui va ordonner d'utiliser preg_split() en dessous. $flags peut contenir RegexIterator::USE_KEY, à ce moment là l'expression régulière est utilisée sur les clés des données d'entrées, et non plus sur les valeurs. $preg_flags peut avoir n'importe quelle valeur des constantes globales PHP PREG_*

II-K. SplFileInfo

Image non disponible

Peu de choses à dire sur cette classe qui permet de donner des informations sur un fichier. Elle est l'implémentation orientée objet de certaines fonctions PHP sur le système de fichiers comme en témoigne son diagramme de classes.
L'héritage de cette classe permettra de monter des structures personnalisées, setInfoClass() et setFileClass() sauront aussi aider à cette tâche. Elle travaille de mèche avec SPlFileObject dont elle peut créer une instance via sa méthode openFile().
SplFileInfo ne fait que donner des informations statistiques sur une entité du système de fichiers sans jamais l'ouvrir au travers d'un descripteur. Ainsi, cette entité peut ne pas exister, SplFileInfo ne sera pas géné.

Exemple banal
Sélectionnez

$file = new SPlFileInfo('/tmp');
echo $file->getType(); // dir
echo $file->getSize(); // 12288 
echo $file->getMTime(); // 1278510104 
echo $file->getBasename(); // tmp
Une ressource inexistante n'inquiète pas SplFileInfo
Sélectionnez
<?php
$file= new SplFileInfo("nimporte-quoi-ici");
// rien de particulier ne se passe
// la plupart des méthodes retourneront NULL sur une telle ressource

DirectoryIterator étend SplFileInfo, se familiariser avec cette dernière permet de mieux apréhender la première.

II-L. SplFileObject

Image non disponible

SplFileObject ouvre un flux et remonte des informations dessus. Elle hérite de SplFileInfo et comme son parent elle propose une implémentation orientée objet de certaines fonctions relatives au système de fichiers (abstrait via l'API des flux de PHP).
Elle est plus intéressante que SplFileInfo car il faut que la ressource existe sinon elle envoie une exception. En revanche elle consomme plus de mémoire puisqu'elle doit obtenir un descripteur (ouvrir réellement la ressource indiquée pour pouvoir par la suite travailler dessus).

Tentative d'ouverture d'un fichier façon procédurale
Sélectionnez

$file = @fopen('foobarbaz');
if (empty($file)) {
    // traitement de l'erreur
}
Tentative d'ouverture d'un fichier façon objet
Sélectionnez

try {
    $file = new SplFileObject('foobarbaz');
} catch (RuntimeException $e) {
    // traitement de l'erreur
}

L'itérateur qu'implémente SplFileObject agit sur les lignes du fichier.

L'itérateur de SPlFileObject
Sélectionnez

$file = new SplFileObject('foobarbaz');
$file->seek(4); // va à la ligne 4;
echo $file->current(); // affiche la ligne 4

// Affichage du reste du fichier
while ($file->valid()) {
     echo $file->current();
     $file->next();
}

II-M. RecursiveDirectoryIterator

Image non disponible

Revenons à la récursivité : RecursiveDirectoryIterator est un itérateur, qui permet la récursivité sur le parcours d'un répertoire.

 
Sélectionnez
<?php
$directory ="path/to/dir"; 
$iter = new RecursiveIteratorIterator(
	 new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::KEY_AS_FILENAME)
	 , RecursiveIteratorIterator::SELF_FIRST);

foreach ($iter as $entry) {
    if ($entry->isDir()) {
        $token = "<b>%s</b>";
    }else{
        $token = "%s";
    }
    echo str_repeat("&nbsp;", 3*$iter->getDepth());
    printf($token."<br>", $entry);
}

Cet exemple crée un arbre au format HTML basique, de tout le dossier voulu, récursivement. Les dossiers le composant sont mis en gras, et la méthode getDepth() de l'itérateur permet de renvoyer sous forme d'entier, la profondeur dans laquelle il se trouve actuellement.
En un mot : merveilleux.
Si vous ne passez pas l'option SELF_FIRST, alors les dossiers listés, ne seront pas retournés, ce sont les « têtes de noeud » et l'itérateur ne les renvoie pas par défaut. Une fois de plus, la documentation nous en apprend beaucoup sur toutes ces petites options.
Aussi, $entry est de type SPLFileInfo, getFilename() renvoie par défaut le chemin complet du fichier, nous avons modifié ce comportement pour obtenir juste le nom du fichier, grâce à une constante passée au constructeur : KEY_AS_FILENAME.

FilesystemIterator peut vous interesser. Il étend DirectoryIterator (pas de récursivité) et lui impose des options comme SKIP_DOTS, KEY_AS_PATHNAME. Très pratique.

II-N. AppendIterator

AppendIterator sert à fusionner plusieurs objets itératifs en un seul.

AppendIterator
AppendIterator
 
Sélectionnez

<?php
$arr1  = new ArrayIterator(array(1, 2, 3));
$arr2  = new ArrayIterator(array(4, 5, 6));

$ai = new AppendIterator();
$ai->append($arr1);
$ai->append($arr2);

foreach ($ai as $value) {
    echo $value; // 123456
}

Ici aussi, nul besoin de faire suivre 2 instructions foreach l'une derrière l'autre : la logique de selection des données est déplacée dans l'itérateur.

Evidemment, la méthode append() attend un objet qui implémente Iterator : vous pouvez donc ajouter des objets itératifs très différents (par exemple un DirectoryIterator + un FilterIterator) dans le AppendIterator, puis itérer sur tous à la suite en itérant sur le sommet AppendIterator

II-O. ParentIterator

ParentIterator
ParentIterator

ParentIterator est capable de ne retourner que les éléments ayant un enfants, depuis un RecursiveIterator quelconque.
Prennons un exemple avec un RecursiveArrayIterator :

 
Sélectionnez

<?php
$arr = array(
         10,
         array(1, 2, 3),
         11,
         array(4, 5, 6),
         12 );
$pi = new ParentIterator(new RecursiveArrayIterator($arr));

foreach ($pi as $key => $val) {
    printf("%s - %s", $key, print_r($val, true));
}
/*
1 - Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
3 - Array
(
    [0] => 4
    [1] => 5
    [2] => 6
)
*/

II-P. CachingIterator

CachingIterator
CachingIterator

Qui n'a jamais souhaité répondre à cette question simple : Je suis dans un itérateur à une position X, "reste-t-il un élément après ma position, ou suis-je le dernier ?" ?
CachingIterator sert à répondre à cette question, il prend en paramètre un itérateur, et va le parcourir tout de suite, d'un coup.
En bouclant après sur le CachingIterator, on pourra alors savoir s'il reste des résultats, grâce à la méthode hasNext(), car celui-ci aura déja fait le tour de tous les éléments de la structure.

 
Sélectionnez
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test','logintest','passtest');
$stmt = new CachingIterator(new IteratorIterator($pdo->query("SELECT * FROM test")));
$stmt->rewind();
$stmt->next();
$stmt->next();

if ($stmt->hasNext()) {
    echo "il reste des résultats";
}

Ici, un objet PDOStatement est utilisé (un jeu de résultats de base de données). Le CachingIterator permet de savoir s'il reste des résultats après avoir avancé 2 fois le pointeur interne.

II-Q. SimpleXMLIterator

SimplexmlIterator
SimplexmlIterator

Cet objet est lié à simpleXML, et est automatiquement instancié lors de la création d'un noeud de SimpleXMLElements. Cependant, on peut l'instancier à part, en lui passant une chaine représentant du XML valide.
Associé à un RecursiveIteratorIterator, on peut alors afficher le contenu de tous les noeuds facilement :

SimpleXMLIterator
SimpleXMLIterator
 
Sélectionnez
<?php
$xml =<<<EOF
<doc>
<a>hello</a>
<a>
	<b>world</b>
	<c />
	<d>
	  <e>coucou</e>
	</d>
</a>
<c>developpez</c>
</doc>
EOF;

$sxml = new RecursiveIteratorIterator(new SimpleXMLIterator($xml));
foreach ($sxml as $val) {
	printf("%s - ",$val); // hello - world - coucou - developpez
}

Evidemment on peut le lier avec d'autres itérateurs.

II-R. ArrayObject

Image non disponible
arrayObject

ArrayObject est un objet très connu et très utile de la SPL. Pour faire simple : utilisez un tableau PHP comme un objet.
Vous pouvez aussi jouer avec son itérateur (interne) et le changer à votre guise.

 
Sélectionnez

<?php
$ar = new ArrayObject(array('a', 'b', 'c'));
$ar['key'] = 'd';

echo count($ar); // 4
$oldArray = $ar->exchangeArray(array(1, 2, 3));
foreach($ar as $value) {
echo $value;
}
// affiche alors 123

En passant une constante spéciale au constructeur, on peut alors utiliser l'accès objet sur cet ArrayObject, en plus de l'accès tableau classique :

 
Sélectionnez
<?php
$ar = new ArrayObject(array('a', 'b', 'c'), ArrayObject::ARRAY_AS_PROPS);
$ar->key = 'd'; //ARRAY_AS_PROPS

echo count($ar); // 4

C'est pratique car quelques fois on se retrouve dans un cas où l'analyseur syntaxique nous bloque, pensez à ceci :
$o->aMethod()['somekey'];
Si aMethod() retourne un array, on ne peut directement accéder à une propriété, sans passer par une valeur intermédiaire (avant PHP 5.3.3). Si on utilise l'ArrayObject correctement, alors on a
$o->aMethod()->somekey;
Ce qui est syntaxiquement valide.
Il est possible de même, de changer l'itérateur de l'arrayObject, par défaut, il s'agit d'un ArrayIterator, qui hérite entre autres de SeekableIterator (héritant d'Iterator). On peut lui inculquer un RecursiveArrayIterator :

 
Sélectionnez
<?php
$ar = new ArrayObject(array('a', 'b', 'c', array('d')));
$ar['key'] = array('some value', 'another value');

$ar->setIteratorClass('RecursiveArrayIterator');
$itere = new RecursiveIteratorIterator($ar);
foreach($itere as $value) {
echo $value;
}
// affiche : abcdsome valueanother value 

L'inconvénient de ArrayObject est que ce n'est pas un tableau PHP, il ne peut être utilisé dans les fonctions pour tableaux de PHP (array_*()). Qu'à cela ne tienne, getArrayCopy() et exchangeArray() permettent respectivement de sortir et d'entrer un tableau de/dans un ArrayObject.

II-S. SplFixedArray

Image non disponible
splFixedArray

Comme son nom l'indique, SplFixedArray permet d'utiliser un objet comme un tableau, mais avec les restrictions suivantes: la taille est fixée et les clés doivent être numériques.
Comme une table de hashage est utilisée dans les tableaux PHP pour stocker les clés, le fait d'utiliser des index numériques évite toute l'étape de calcul de la clé. Coté rangement, comme le tableau est fixé, la structure sous-jascente sera beaucoup plus performante qu'une liste chainée qui se déplace tout le temps en mémoire utilisée pour les tableaux PHP (array).

En gros, si vous connaissez à l'avance le nombre maximal d'entités à stocker, et que vous n'avez pas besoin de clés de stockage litérales, alors SPlFixedArray sera beaucoup plus intéressant qu'un tableau PHP ou un ArrayObject.

 
Sélectionnez
<?php
$array = new SplFixedArray(4);
$array[] = "foo";
$array[] = "bar";
echo $array[8]; // RuntimeException

$array->setSize(10);
$array[8] = "dvp";
var_dump($array);

Comme on le voit, il implémente toutes les interfaces pour se faire passer pour un tableau, ainsi que pour entrer/sortir un tableau (un vrai) depuis l'objet (toArray(), fromArray()), utile lorsqu'on veut utiliser des fonctions de tableaux (qui ne fonctionnent pas sur SplFixedArray)

II-T. SplObjectStorage

SplObjectStorage est un conteneur itératif d'objets. Son avantage est qu'il s'occupe de stocker et de retrouver un objet, sans que l'on ait besoin de s'occuper de cela.
Lorsqu'un objet contient des instances d'autres objets, SplObjectStorage est l'endroit idéal pour les stocker. Plus simple qu'un tableau ou un ArrayObject, ce support de stockage s'occupe notament d'éviter de stocker 2 mêmes instances, et leur suppression est très simple :

splobjectstorage
splobjectstorage
 
Sélectionnez
<?php
class Panier extends SplObjectStorage
{
    public function attach($i)
    {
        if (!$i instanceof Item) {
            throw new Exception('Item requis');
        }
        return parent::attach($i);
    }
    
    public function detach($i)
    {
        if (!$i instanceof Item) {
            throw new Exception('Item requis');
        }
        return parent::detach($i);
    }
    
    public function calculTotal()
    {
        foreach ($this as $item) {
            $somme[] = $item->price;
        }
        return array_sum($somme);
    }
}

La méthode attach() se charge de ne pas stocker deux mêmes instances (aucune exception n'est retournée). Quant à detach(), elle se charge de retrouver l'objet qu'on lui passe en paramètre (s'il existe) pour le supprimer du conteneur.
Comme le tableau, cette classe permet la fusion(addAll()), la recherche(contains()), la différence(removeAll()) mais pas l'intersection. Elle est bien plus pratique qu'il n'y parait, elle peut servir par exemple de support pour un design pattern registre ou pour toute agrégation à composante multiple (n,m).

SplObjectStorage implémente la notion mathématique/informatique "d'ensemble" ainsi que la table de hashage.

SplObjectStorage n'accepte que des objets. Tout autre type enverra une erreur PHP.

II-U. SplStack

splStack
splStack

L'implémentation de la pile en PHP.

SplStack étend SplDoublyLinkedList qui implémente la liste doublement chainée en PHP.

Rappel: En informatique, une pile est une structure fondamentale dans laquelle les éléments s'empilent et se dépilent "par le haut": on ne peut à un moment donner qu'accéder à l'élément sur le dessus, on parle alors de LIFO (Last In First Out).

La pile en PHP
Sélectionnez
<?php
$s = new SplStack();
$s[] = "foo";
$s[] = "bar";
$s['a'] = 'dvp' // Exception, les index littéraux ne sont pas acceptés (cela n'a pas de sens)
$s->push("baz");
$s->unshift("first");

foreach ($s as $val) {
    echo $val;
}
// Affiche bazbarfoofirst

On peut même dire à l'itérateur d'effacer les éléments de la pile pendant le parcours :

 
Sélectionnez

$s = new SplStack();
$s[] = "foo";
$s[] = "bar";
$s->setIteratorMode(SplDoublyLinkedList::IT_MODE_DELETE|SplDoublyLinkedList::IT_MODE_LIFO);
foreach ($s as $val) {
    echo $val; // Affiche foobar
}
foreach ($s as $val) {
    echo $val; // N'affiche plus rien, la pile a été vidée par la précédente itération
}

Le mode d'itération d'une pile est IT_MODE_LIFO, si vous changez celà avec setIteratorMode(), il ne s'agit alors plus d'une pile, et une exception vous sera envoyée.

II-V. SplQueue

splQueue
splQueue

L'implémentation de la file en PHP.

SplQueue étend SplDoublyLinkedList qui implémente la liste doublement chainée en PHP.

Rappel: En informatique, une file est une structure fondamentale dans laquelle les éléments se tassent "par le haut": on ne peut à un moment donner qu'accéder à l'élément le plus bas, on parle alors de FIFO (First In First Out). Tout comme une file d'attente ;-)

La file en PHP
Sélectionnez

$s = new SplQueue();
$s[] = "foo";
$s[] = "bar";
$s['a'] = 'dvp' // Exception, les index littéraux ne sont pas acceptés (cela n'a pas de sens)
$s->push("baz");
$s->unshift("first");

foreach ($s as $val) {
    echo $val;
}
// Affiche firstfoobarbaz

Tout comme pour SplStack, vous pouvez dire à l'itérateur d'effacer les éléments de la file pendant le parcours, au moyen de setIteratorMode().

II-W. SplHeap

splHeap
splHeap

L'implémentation du tas en PHP.

SplHeap est une classe abstraite servant de base à l'implémentation d'un tas.
SplMaxHeap, SplMinHeap sont des implémentations concrètes de tas. SplPriorityQueue aussi, mais elle n'hérite pas de SplHeap.

Rappel: En informatique, un tas est une liste de valeurs à l'image d'une file ou d'une pile, mais l'ordre dans lequel ces valeurs entrent dans la structure est déterminé par un algorithme utilisateur (fonction de tri). Les files et piles sont des sous-ensembles du tas.

Un tas en PHP (tas minimum)
Sélectionnez

$s = new SplMinHeap();
$s->insert(3);
$s->insert(1);
$s->insert(8);

foreach ($s as $val) {
    echo $val;
}
// Affiche 138

Attention, itérer sur un tas le vide. Ce comportement n'est pas changeable.

Un tas personnalisé
Sélectionnez
<?php
class Membre
{
    public $nbreMessages;
}

class MembreHeap extends SplHeap
{
    // Classement par rapport au nombre de messages
    public function compare(Membre $m1, Membre $m2)
    {
         if ($m1->nbreMessages < $m2->nbreMessages) {
             return -1;
         } elseif ($m1->nbreMessages > $m2->nbreMessages) {
             return 1;
         }
         return 0;
    }
}

$m1 = new Membre; $m1->nbreMessages = 18;
$m2 = new Membre; $m2->nbreMessages = 6;
$m3 = new Membre; $m3->nbreMessages = 11;

$heap = new MembreHeap;
$heap->insert($m1);
$heap->insert($m2);
$heap->insert($m3);

foreach ($heap as $membre) {
    var_dump($membre);
}

/* Affiche:
object(Membre)#1 (1) {
  ["nbreMessages"]=>
  int(18)
}
object(Membre)#3 (1) {
  ["nbreMessages"]=>
  int(11)
}
object(Membre)#2 (1) {
  ["nbreMessages"]=>
  int(6)
}
*/

Voyez SplPriorityQueue qui est un tas permettant de définir un poids aux entités qu'on y insère. Ce poids est alors utilisé pour ordonner les entités dans le tas.

II-X. Fonctions de la SPL

L'extension SPL rajoute des classes et des interfaces à PHP, mais pas seulement : quelques fonctions existent aussi.
La plus intéressante reste sans aucun doute iterator_to_array(), qui prend en paramètre un itérateur et le parcourt totalement pour sortir ses résultats sous forme de tableau. En gros : ceci évite un foreach inutile juste pour regarder les résultats :

 
Sélectionnez

$it = new ArrayIterator(array('foo'=>'bar', 'baz'=>'developpez', 'julien'=>'pauli');
var_dump(iterator_to_array($it));

Suivant le même exemple : iterator_count($iterator) compte le nombre d'éléments dans la structure itérative.
spl_object_hash() retourne un identifiant unique pour un objet. 2 mêmes instances mémoires auront les mêmes identifiants.
spl_autoload_register() permet de gérer une pile d'autoloads (fonctions étant chargées de charger une classe de manière automatique)

II-Y. Exceptions SPL

splobjectstorage
splobjectstorage

La SPL rajoute aussi à PHP des classes d'exception. Elle les utilise pour elle-même, mais permet aussi au développeur de les utiliser.
Elles se décomposent en 2 grandes familles :

  1. LogicException: Les erreurs relatives à la configuration de l'objet
  2. RuntimeException: Les erreurs relatives à la vie de l'objet

Voyons un petit exemple:

 
Sélectionnez

class Foo
{
    protected $baz = 0;
    protected $bar = null;

    public function __construct($options)
    {
        /** Envoyer des LogicException ici **/
    }

    public function setBaz($baz)
    {
        /** Envoyer des LogicException ici **/
    }

    public function setBar(Bar $bar)
    {
        /** Envoyer des LogicException ici **/
    }

    public function doSomething($parameter)
    {
        if ($parameter != $someValue) {
            /** Envoyer des LogicException ici **/
        }
        /* Ici l'objet vit et effectue des actions
         * Si une action échoue, envoyer une RuntimeException */
    }
}

Evidemment, il reste possible et recommandé de créer ses Exceptions personnalisées en créant des classes qui étendent les bonnes classes d'exception de la SPL.
Dans un applicatif, la hiérarchie des exceptions est très importantes car le type de l'exception renseigne sur le type d'erreur rencontré par le programme.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2008 . Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.