Comment écrire vos propres objets itérables en PHP –
PHP vous permet de créer des objets itérables. Ceux-ci peuvent être utilisés dans des boucles au lieu de tableaux scalaires. Les itérables sont couramment utilisés comme collections d’objets. Ils vous permettent de saisir cet objet tout en conservant la prise en charge de la boucle.
Sommaire
Itération simple
Pour parcourir un tableau en PHP, vous utilisez un foreach
boucle:
foreach (["1", "2", "3"] as $i) { echo ($i . " "); }
Cet exemple émettrait 1 2 3
.
Vous pouvez également parcourir un objet:
$cls = new StdClass(); $cls -> foo = "bar"; foreach ($cls as $i) { echo $i; }
Cet exemple émettrait bar
.
Le problème de la collecte
Pour les classes de base avec des propriétés publiques, un simple foreach
fonctionne bien. Considérons maintenant une autre classe:
class UserCollection { protected array $items = []; public function add(UserDomain $user) : void { $this -> items[] = $user; } public function containsAnAdmin() : bool { return (count(array_filter( $this -> items, fn (UserDomain $i) : bool => $i -> isAdmin() )) > 0); } }
Cette classe représente une collection de UserDomain
les instances. Comme PHP ne prend pas en charge les tableaux typés, des classes comme celle-ci sont nécessaires lorsque vous voulez taper un tableau qui ne peut contenir qu’un seul type de valeur. Les collections vous aident également à créer des méthodes utilitaires, comme containsWithAdmin
, qui facilitent l’interaction naturelle avec les éléments du tableau.
Malheureusement, essayer de parcourir cette collection ne donnera pas les résultats escomptés. Idéalement, l’itération devrait fonctionner sur le $items
tableau, pas la classe elle-même.
Implémentation d’itérateur
L’itération naturelle peut être ajoutée à l’aide du Iterator
interface. En mettant en œuvre Iterator
, vous pouvez définir le comportement de PHP lorsque des instances de votre classe sont utilisées avec foreach
.
Iterator
a cinq méthodes que vous devrez implémenter:
current() : mixed
– Récupère l’élément à la position actuelle dans l’itération.key() : scalar
– Obtenez la clé à la position actuelle dans l’itération.next() : void
– Passez à la position suivante dans l’itération.rewind() : void
– Rembobinez la position au début de l’itération.valid() : bool
– Obtenir si la position actuelle a une valeur.
Ces méthodes peuvent prêter à confusion au début. Leur mise en œuvre est cependant simple – vous spécifiez ce qu’il faut faire à chaque étape d’un foreach
exécution.
Chaque fois que votre objet est utilisé avec foreach
, rewind()
sera appelé. Le valid()
est appelée ensuite, informant PHP s’il y a une valeur à la position actuelle. S’il y a, current()
et key()
sont appelés pour obtenir la valeur et la clé à cette position. Finalement, le next()
est appelée pour faire avancer le pointeur de position. La boucle revient à l’appel valid()
pour voir si un autre article est disponible.
Voici une implémentation typique de Iterator
:
class DemoIterator { protected int $position = 0; protected array $items = ["cloud", "savvy"]; public function rewind() : void { echo "Rewinding"; $this -> position = 0; } public function current() : string { echo "Current"; return $this -> items[$this -> position]; } public function key() : int { echo "Key"; return $this -> position; } public function next() : void { echo "Next"; ++$this -> position; } public function valid() : void { echo "Valid"; return isset($this -> items[$this -> position]); } }
Voici ce qui se passerait lors de l’itération DemoIterator
:
$i = new DemoIterator(); foreach ($i as $key => $value) { echo "$key $value"; } // EMITS: // // Rewind // Valid Current Key // 0 cloud // Next // // Valid Current Key // 1 savvy // Next // // Valid
Votre itérateur doit conserver un enregistrement de la position de la boucle, vérifiez s’il y a un élément à la position actuelle de la boucle (via valid()
) et renvoyer la clé et la valeur à la position actuelle.
PHP n’essaiera pas d’accéder à la clé ou à la valeur lorsque la position de la boucle n’est pas valide. Retour false
de valid()
met immédiatement fin au foreach
boucle. En règle générale, ce sera lorsque vous arrivez à la fin du tableau.
IteratorAggregate
L’écriture d’itérateurs devient rapidement répétitive. La plupart suivent la recette exacte indiquée ci-dessus. IteratorAggregate
est une interface qui vous aide à créer rapidement des objets itérables.
Mettre en œuvre le getIterator()
méthode et renvoyer un Traversable
(l’interface de base de Iterator
). Il sera utilisé comme itérateur lorsque votre objet est utilisé avec foreach
. C’est généralement le moyen le plus simple d’ajouter une itération à une classe de collection:
class UserCollection implements IteratorAggregate { protected array $items = []; public function add(UserDomain $User) : void { $this -> items[] = $user; } public function getIterator() : Traversable { return new ArrayIterator($this -> items); } } $users = new UserCollection(); $users -> add(new UserDomain("James")); $users -> add(new UserDomain("Demo")); foreach ($users as $user) { echo $user -> Name; }
Lorsque vous travaillez avec des collections, trois lignes de code suffisent généralement pour configurer l’itération! Une ArrayIterator
est retourné comme Traversable
. C’est une classe qui crée automatiquement un Iterator
hors d’un tableau. Vous pouvez désormais parcourir les valeurs logiques de votre objet, au lieu des propriétés directes de l’objet.
Utilisation d’itérateurs PHP prédéfinis
Vous devrez écrire vos propres itérateurs si vous avez une logique complexe. Il est rarement nécessaire de repartir de zéro car PHP est livré avec plusieurs itérateurs avancés fournis par SPL.
Les classes intégrées incluent DirectoryIterator
, FilesystemIterator
, GlobIterator
et divers itérateurs récursifs. Voici quelques-uns des itérateurs génériques les plus utiles.
LimitIterator
Le LimitIterator
vous permet d’itérer sur un sous-ensemble d’un tableau. Vous n’avez pas besoin d’épisser le tableau en premier ou de suivre manuellement la position à l’intérieur de votre foreach
.
$arr = new ArrayIterator(["a", "b", "c", "d"]); foreach (new LimitIterator($arr, 0, 2) as $val) { echo $val; }
Cet exemple émettrait a b
. Notez que LimitIterator
accepte un autre Iterator
, pas un tableau. L’exemple utilise ArrayIterator
, la classe intégrée qui construit un Iterator
à partir d’un tableau.
InfiniteIterator
InfiniteIterator
ne termine jamais la boucle, vous devrez donc break
à partir de celui-ci manuellement. Sinon, l’itérateur revient automatiquement au début du tableau lorsqu’il atteint la fin.
Cet itérateur est particulièrement utile lorsque vous travaillez avec des valeurs basées sur le temps. Voici un moyen simple de créer un calendrier de trois ans, qui utilise également le LimitIterator
décrit ci-dessus:
$months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; $infinite = new InfiniteIterator(new ArrayIterator($months)); foreach (new LimitIterator($infinite, 0, 36) as $month) { echo $month; }
Cela équivaut à trois ans de mois.
FilterIterator
FilterIterator
est une classe abstraite que vous devez étendre. Mettre en œuvre le accept
méthode pour filtrer les valeurs indésirables qui doivent être ignorées lors de l’itération.
class DemoFilterIterator extends FilterIterator { public function __construct() { parent::__construct(new ArrayIterator([1, 10, 4, 6, 3])); } public function accept() { return ($this -> getInnerIterator() -> current() < 5); } } $demo = new DemoFilterIterator(); foreach ($demo as $val) { echo $val; }
Cet exemple émettrait 1 4 3
.
Les valeurs de tableau supérieures à 5 sont filtrées et n’apparaissent pas dans le foreach
.
Le type «itérable»
Parfois, vous pouvez écrire une fonction générique qui utilise un foreach
boucle mais ne sait rien des valeurs sur lesquelles il sera itéré. Un exemple est un gestionnaire d’erreurs abstrait qui vide simplement les valeurs qu’il reçoit.
Vous pouvez taper iterable
dans ces scénarios. C’est un pseudo-type qui acceptera soit un array
ou tout objet implémentant Traversable
:
function handleBadValues(iterable $values) : void { foreach ($values as $value) { var_dump($value); } }
Le iterable
le type est si vague que vous devez réfléchir attentivement avant de l’utiliser. Néanmoins, il peut être utile comme type de dernier recours si vous devez garantir qu’une valeur d’entrée fonctionnera avec foreach
.
Conclusion
L’utilisation d’itérateurs vous aide à écrire du code propre et plus modulaire. Vous pouvez déplacer des méthodes qui agissent sur des tableaux d’objets dans des classes de collection dédiées. Ceux-ci peuvent ensuite être tapés individuellement tout en restant entièrement compatibles avec foreach
.
La plupart du temps, l’ajout d’un support d’itération peut être obtenu en implémentant IteratorAggregate
et renvoyer un ArrayIterator
configuré avec les éléments de votre collection. Les autres types d’itérateurs de PHP, qui ont tendance à être inutilisés par les développeurs, peuvent grandement simplifier des boucles plus spécifiques. Ils offrent des comportements logiques complexes sans nécessiter de suivi manuel du pointeur dans votre foreach
déclaration.