PHP 5.3 : Nouveautes de la SPL (partie 2)
18 novembre 2008 —Les exemples correspondant à ce point se trouvent dans le répertoire "spl".
Voici la seconde partie de cet article traitant des nouveautés que PHP 5.3 ajoute à la SPL, la Standard PHP Library -- la première partie ayant été publiée hier matin.
Sommaire de cette seconde partie :
SplQueue
En restant dans le même registre, mais sans avoir de notion de priorisation cette fois, passons à la classe SplQueue
, qui permet de gérer une queue des plus standard[1].
Cette fois-ci, je présenterai un exemple un peu plus long, histoire d'illustrer un peu mieux ce que donne l'utilisation de ces nouveaux ajouts à la SPL.
Commençons par définir une fonction affichant le contenu d'une queue :
function outputQueue($q) {
echo '<hr />File :';
foreach ($q as $element) {
var_dump($element);
}
}
Nous pourrons appeler cette fonction aussi souvent que voulu sur une instance de SplQueue
, parce que le parcours d'une queue non priorisée ne la vide pas -- contrairement à ce qu'il se passait pour plusieurs des classes vues jusqu'à présent (c'est justement pour cela que je prends celle-ci pour cet exemple : il est plus facile de comprendre ce qu'il se passe si on peut effectuer des sorties écran sans vider la file à chaque fois ^^ ).
Ceci est dû au fait que SplQueue
étend SplDoublyLinkedList
; elle reprend donc le comportement de cette première structure de données.
Commençons par déclarer une instance de SplQueue
, et ajoutons-lui quelques éléments :
- Un nombre entier,
- une liste,
- et un objet.
On notera que l'on peut utiliser la méthode enqueue
pour ajouter des éléments en fin de file, et que l'on peut aussi utiliser l'opérateur []
: SplQueue
implémente l'interface ArrayAccess
.
$q = new SplQueue();
$q[] = 10;
$q->enqueue(array(10, 'glop', 30));
$q[] = new stdClass();
outputQueue($q);
En affichant la file, nous obtenons ceci :
File :
int 10
array
0 => int 10
1 => string 'glop' (length=4)
2 => int 30
object(stdClass)[2]
La queue avec laquelle nous travaillons contient bien trois entrées, et la première ajoutée est première à apparaitre lors du parcours (FIFO, donc).
Ajoutons un élément de plus :
$q[] = 'Hello, World!';
outputQueue($q);
Il vient s'ajouter en fin de file :
File :
int 10
array
0 => int 10
1 => string 'glop' (length=4)
2 => int 30
object(stdClass)[2]
string 'Hello, World!' (length=13)
Et si maintenant nous voulons extraire une entrée de la file, faisons appel à la méthode dequeue
:
$a = $q->dequeue();
echo '<hr />Elément "dequeued" :';
var_dump($a);
outputQueue($q);
L'élément à sortir est le premier qui avait été ajouté, et la file ne contient plus que trois entrées -- les trois ajoutées en dernier :
Elément "dequeued" :
int 10
File :
array
0 => int 10
1 => string 'glop' (length=4)
2 => int 30
object(stdClass)[2]
string 'Hello, World!' (length=13)
Passons à l'extraction d'un second élément :
$a = $q->dequeue();
echo '<hr />Elément "dequeued" :';
var_dump($a);
outputQueue($q);
Plus que deux éléments dans la file, et c'est toujours en tête de file que nous extrayons les données :
Elément "dequeued" :
array
0 => int 10
1 => string 'glop' (length=4)
2 => int 30
File :
object(stdClass)[2]
string 'Hello, World!' (length=13)
Et si nous continuons :
$a = $q->dequeue();
echo '<hr />Elément "dequeued" :';
var_dump($a);
outputQueue($q);
Presque vide :
Elément "dequeued" :
object(stdClass)[2]
File :
string 'Hello, World!' (length=13)
Encore une fois :
$a = $q->dequeue();
echo '<hr />Elément "dequeued" :';
var_dump($a);
outputQueue($q);
Nous avons récupéré le tout dernier élément inséré, et notre file est maintenant vide :
Elément "dequeued" :
string 'Hello, World!' (length=13)
File :
Et que se passe-t-il si nous essayons de dé-filer encore une fois :
// La file est vide, à présent
$a = $q->dequeue(); // RuntimeException: Can't shift from an empty datastructure
Lorsque nous essayons de dé-filer un élément d'une SplQueue
vide, nous prenons une RuntimeException
:
Pensez-y, et n'hésitez pas à utiliser les méthodes count
pour savoir combien d'éléments il reste dans votre file, ou isEmpty
pour vous assurer qu'elle n'est pas vide !
Par exemple :
if ($q->isEmpty()) {
echo '<hr />';
echo 'La file est vide !';
}
SplStack
Passons maintenant à la classe SplStack
, qui implémente fort logiquement une pile LIFO.
Ici aussi, cette classe hérite de SplDoublyLinkedList
, et peut donc être parcourue sans que cela ne la vide, ce qui va nous permettre de rentrer un peu dans les détails sur un exemple :
Tout d'abord, comme précédemment, une fonction pour afficher le contenu de la pile :
function outputStack($q) {
echo '<hr />Pile :';
foreach ($q as $element) {
var_dump($element);
}
}
Et maintenant, instancions la classe SplStack
, et ajoutons lui quelques éléments.
Nous pouvons soit utiliser la méthode push
, soit passer par l'opérateur []
, puisque SplStack
implémente @@ArrayAccess@ :
$q = new SplStack();
$q[] = 10;
$q->push(array(10, 'glop', 30));
$q[] = new stdClass();
outputStack($q);
Notre pile nouvellement créée a le contenu suivant :
Les éléments apparaissent à l'affichage dans l'ordre inverse de leurs insertions : LIFO
Pile :
object(stdClass)[2]
array
0 => int 10
1 => string 'glop' (length=4)
2 => int 30
int 10
Ajoutons un élément :
$q[] = 'Hello, World!';
outputStack($q);
Notre nouvel élément est inséré en dernier ; il apparait donc en premier lors du parcours de la pile :
Pile :
string 'Hello, World!' (length=13)
object(stdClass)[2]
array
0 => int 10
1 => string 'glop' (length=4)
2 => int 30
int 10
Et si nous commençons à extraire des éléments de la pile, avec la méthode pop
:
$a = $q->pop();
echo '<hr />Elément "popé" :';
var_dump($a);
outputStack($q);
Le premier élément à sortir est le dernier inséré :
Elément "popé" :
string 'Hello, World!' (length=13)
Pile :
object(stdClass)[2]
array
0 => int 10
1 => string 'glop' (length=4)
2 => int 30
int 10
Je vous dispense du dépilage des trois éléments suivants -- vous aurez déjà compris dans quel ordre ils sortiront, j'espère ;-)
SplFixedArray
Avant de terminer, pour la route, une petite dernière ^^
(Dont j'ai à peine entendu parler, je dois avouer : elle n'est à priori pas documentée sur php.net, et je n'ai pas le souvenir de l'avoir vue mentionnée ailleurs que dans un article mis en ligne il y a quelques jours sur le DeveloperWorks d'IBM)
La classe SplFixedArray
permet de représenter des tableaux répondant à deux conditions :
SplFixedArray
ne permet de stocker des listes que de taille fixe,- Et seuls des nombres entiers peuvent être utilisés comme clefs de ces tableaux.
Finalement, on s'éloigne des tableaux associatifs auxquels nous sommes habitués avec array
, et nous rapprochons plus de la notion de tableaux à laquelle nous pouvions être habitués avec des langages tels C[2].
SplFixedArray : Principes de base
L'idée de la classe SplFixedArray
est que la taille de la liste doit être précisée lors de sa création :
$arr = new SplFixedArray(4);
Il est ensuite possible d'ajouter des éléments, en utilisant des nombres entiers, strictement inférieurs au nombre indiqué à la création de la SplFixedArray
:
$arr[0] = 'première chaine';
$arr[1] = 'seconde chaine';
$arr[2] = 'troisième chaine';
$arr[3] = 'quatrième chaine';
var_dump($arr);
Et le var_dump
nous donnera en sortie :
object(SplFixedArray)[1]
string 'première chaine' (length=16)
string 'seconde chaine' (length=14)
string 'troisième chaine' (length=17)
string 'quatrième chaine' (length=17)
Quant au parcours, il se fait à l'aide de foreach
, sans différence par rapport à ce à quoi nous sommes habitués :
foreach ($arr as $key => $value) {
var_dump($key, $value);
}
Qui donne :
int 0
string 'première chaine' (length=16)
int 1
string 'seconde chaine' (length=14)
int 2
string 'troisième chaine' (length=17)
int 3
string 'quatrième chaine' (length=17)
Sans surprise, la classe SplFixedArray
implémente les interfaces suivantes :
On peut donc, globalement, l'utiliser en conservant les habitudes acquises avec les array
.
A noter
Deux points sont à noter lorsque l'on souhaite travailler avec la classe SplFixedArray
.
Tout d'abord, si vous essayez de travailler avec un indice supérieur à la contenance de la liste, vous obtiendrez une RuntimeException
.
Même chose si vous souhaitez utiliser autre chose qu'un entier comme clef pour un des éléments d'une instance de SplFixedArray
, ou si vous voulez ne pas spécifier d'indice, en croyant ajouter un élément en fin de liste.
Cela signifie que cette portion de code :
$arr = new SplFixedArray(4);
$arr[0] = 'première chaine';
$arr[1] = 'seconde chaine';
$arr[2] = 'troisième chaine';
$arr[3] = 'quatrième chaine';
$arr[4] = 'cinquième chaine';
var_dump($arr);
Et celle-ci :
$arr = new SplFixedArray(4);
$arr[0] = 'première chaine';
$arr[] = 'seconde chaine';
$arr[2] = 'troisième chaine';
$arr[3] = 'quatrième chaine';
var_dump($arr);
Léveront toutes deux une exception de la forme suivante :
Notez aussi que même si la taille d'une SplFixedArray
est définie à sa construction, vous pouvez la modifier ultérieurement, à l'aide de la méthode setSize
, comme ceci :
$arr = new SplFixedArray(4);
$arr[0] = 'première chaine';
$arr[1] = 'seconde chaine';
$arr[2] = 'troisième chaine';
$arr[3] = 'quatrième chaine';
var_dump($arr);
$arr->setSize(6);
$arr[4] = 'cinquième chaine';
$arr[5] = 'sixième chaine';
var_dump($arr);
Ce qui donnera l'affichage suivant :
object(SplFixedArray)[1]
string 'première chaine' (length=16)
string 'seconde chaine' (length=14)
string 'troisième chaine' (length=17)
string 'quatrième chaine' (length=17)
object(SplFixedArray)[1]
string 'première chaine' (length=16)
string 'seconde chaine' (length=14)
string 'troisième chaine' (length=17)
string 'quatrième chaine' (length=17)
string 'cinquième chaine' (length=17)
string 'sixième chaine' (length=15)
Et au niveau des performances ?
Pour ce qui est des performances, dans un cas où vous créez souvent de nouvelles listes, ne contenant que peu d'éléments, utiliser une instance de SplFixedArray
-- et donc, une instanciation de classe -- vous coûtera probablement plus cher que de travailler avec un array
.
Par exemple, avec la portion de code suivante, qui créée beaucoup de petites listes, auxquelles seuls quelques éléments sont ajoutés :
define('NB_ITERATIONS', 100000);
function testSplFixedArray()
{
for ($i=0 ; $i<NB_ITERATIONS ; $i++) {
$arr = new SplFixedArray(4);
$arr[0] = 'première chaine';
$arr[1] = 'seconde chaine';
$arr[2] = 'troisième chaine';
$arr[3] = 'quatrième chaine';
}
}
function testArray()
{
for ($i=0 ; $i<NB_ITERATIONS ; $i++) {
$arr = array();
$arr[0] = 'première chaine';
$arr[1] = 'seconde chaine';
$arr[2] = 'troisième chaine';
$arr[3] = 'quatrième chaine';
}
}
testSplFixedArray(); // 89% du temps
testArray(); // 7% du temps
Nous obtenons le graphe de profiling suivant :
Nous constatons que la plus grosse partie du temps (de l'ordre de 90%) du script est passé dans la version utilisant SplFixedArray
.
Par contre, si vous partez dans la direction inverse, en ne créant que peu souvent des listes, mais que leur taille est grande, travailler avec des SplFixedArray
peut devenir intéressant : leur taille étant fixée à la création, il n'est pas nécessaire de les redimensionner << à la volée >>, contrairement aux array
.
Par exemple, avec la portion de code suivante :
define('NB_ITERATIONS', 1000);
function testSplFixedArray()
{
for ($i=0 ; $i<NB_ITERATIONS ; $i++) {
$arr = new SplFixedArray(10000);
for ($j=0 ; $j<1000 ; $j++) {
$arr[$j] = 'glop';
}
}
}
function testArray()
{
for ($i=0 ; $i<NB_ITERATIONS ; $i++) {
$arr = array();
for ($j=0 ; $j<10000 ; $j++) {
$arr[$j] = 'glop';
}
}
}
testSplFixedArray(); // 45% du temps
testArray(); // 55% du temps
Nous arrivons au graphe de profiling reproduit ci-dessous :
Et nous constatons que la tendance s'inverse, en faveur de l'utilisation de SplFixedArray
.
Ici encore, c'est à l'usage que les éventuels gains réels apparaitront... Vivement l'usage, donc !