Comment fonctionne RecursiveIteratorIterator en PHP?
Comment fonctionne RecursiveIteratorIterator
?
Le manuel PHP n'a rien de bien documenté ou expliqué. Quelle est la différence entre IteratorIterator
et RecursiveIteratorIterator
?
4 réponses
RecursiveIteratorIterator
est un béton Iterator
la mise en œuvre de l'arbre transversal. Il permet à un programmeur de traverser un objet conteneur qui implémente l'interface RecursiveIterator
, voir Iterator dans Wikipedia pour les principes généraux, les types, la sémantique et les modèles d'itérateurs.
Dans la différence de IteratorIterator
qui est un béton Iterator
implémentant la traversée d'objet dans un ordre linéaire (et acceptant par défaut tout type de Traversable
dans son constructeur), le RecursiveIteratorIterator
permet de boucler tous les nœuds dans un arbre ordonné {[138] } d'objets et son constructeur prend un RecursiveIterator
.
En bref: RecursiveIteratorIterator
permet de vous faire une boucle sur un arbre, IteratorIterator
permet de vous faire une boucle sur une liste. Je montre cela avec quelques exemples de code ci-dessous bientôt.
Techniquement, cela fonctionne en sortant de la linéarité en traversant tous les enfants d'un nœud (le cas échéant). Ceci est possible car par définition tous les enfants d'un nœud sont à nouveau un RecursiveIterator
. Le toplevel {[20] } empile ensuite en interne les différents RecursiveIterator
s par leur profondeur et garde un pointeur sur le sous-actif actuel Iterator
pour la traversée.
Cela permet de visiter tous les nœuds d'un arbre.
Les principes sous-jacents sont les mêmes qu'avec IteratorIterator
: une interface spécifie le type d'itération et la classe d'itérateur de base est l'implémentation de ces sémantiques. Comparez avec les exemples ci-dessous, pour une boucle linéaire avec foreach
, vous ne pensez normalement pas au l'implémentation détaille beaucoup moins que vous n'ayez besoin de définir un nouveau Iterator
(par exemple quand un type concret lui-même n'implémente pas Traversable
).
Pour la traversée récursive-à moins que vous n'utilisiez un Traversal
prédéfini qui a déjà une itération de traversée récursive-vous avez normalement besoin de pour instancier l'itération RecursiveIteratorIterator
existante ou même écrire une itération de traversée récursive qui est la vôtre Traversable
pour avoir ce type d'itération de traversée avec foreach
.
astuce: vous n'avez probablement pas implémenté l'un ni l'autre, donc cela pourrait être quelque chose à faire pour votre expérience pratique des différences qu'ils ont. Vous trouverez une suggestion de bricolage à la fin de la réponse.
Différences Techniques en bref:
- Tout
IteratorIterator
prend toutTraversable
linéaire transversal,RecursiveIteratorIterator
besoins plus spécifiques,RecursiveIterator
faire une boucle sur un arbre. - Où
IteratorIterator
expose son principalIterator
pargetInnerIerator()
,RecursiveIteratorIterator
fournit le sous-Iterator
actif courant uniquement via cette méthode. - alors que
IteratorIterator
n'est totalement pas au courant de quelque chose comme parent ou enfants,RecursiveIteratorIterator
sait comment obtenir et traverser les enfants aussi. -
IteratorIterator
n'a pas besoin d'une pile de itérateurs,RecursiveIteratorIterator
a une pile et sait l'actif sous-itérateur. - où
IteratorIterator
a son ordre en raison de la linéarité et pas de choix,RecursiveIteratorIterator
a un choix pour une traversée ultérieure et doit décider par chaque nœud (décidé via le mode parRecursiveIteratorIterator
). -
RecursiveIteratorIterator
a plus de méthodesIteratorIterator
.
Pour résumer: RecursiveIterator
est un type concret d'itération (en boucle sur un arbre) qui fonctionne sur ses propres itérateurs, à savoir RecursiveIterator
. C'est le même principe qu'avec les IteratorIerator
, mais le type d'itération est différent (ordre linéaire).
Idéalement, vous pouvez aussi créer votre propre ensemble. La seule chose nécessaire est que votre itérateur implémente Traversable
ce qui est possible via Iterator
ou IteratorAggregate
. Ensuite, vous pouvez utilisez-le avec foreach
. Par exemple, une sorte d'objet d'itération récursive à travers l'arbre ternaire avec l'interface d'itération correspondante pour l'objet conteneur(s).
Passons en revue avec quelques exemples réels qui ne sont pas si abstraits. Entre les interfaces, les itérateurs concrets, les objets conteneur et la sémantique d'itération, ce n'est peut-être pas une si mauvaise idée.
Prenez une liste de répertoires comme exemple. Considérez que vous avez l'arborescence de fichiers et de répertoires suivante sur le disque:
Alors qu'un itérateur avec un ordre linéaire traverse simplement le dossier et les fichiers du niveau supérieur( une liste de répertoires unique), l'itérateur récursif traverse également les sous-dossiers et liste tous les dossiers et fichiers (une liste de répertoires avec des listes de ses sous-répertoires):
Non-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Vous pouvez facilement comparer cela avec IteratorIterator
qui ne fait pas de récursivité pour parcourir l'arborescence des répertoires. Et le {[19] } qui peut traverser dans l'arbre comme le Récursive liste affiche.
Abord un exemple très simple avec un DirectoryIterator
qui met en œuvre Traversable
ce qui permet foreach
de itérer sur elle:
$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
L'exemple de sortie pour la structure de répertoire ci-dessus est alors:
[tree]
├ .
├ ..
├ dirA
├ fileA
Comme vous le voyez, cela n'utilise pas encore IteratorIterator
ou RecursiveIteratorIterator
. Au lieu de cela, il suffit d'utiliser foreach
qui fonctionne sur l'interface Traversable
.
Comme foreach
par défaut ne connaît que le type de l'itération nommée ordre linéaire, on peut spécifier le type d'itération explicitement. À première vue, cela peut sembler trop verbeux, mais à des fins de démonstration (et pour faire la différence avec RecursiveIteratorIterator
plus visible plus tard), spécifions le type d'itération linéaire en spécifiant explicitement le type d'itération IteratorIterator
pour la liste de répertoires:
$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Cet exemple est presque identique au premier, la différence est que $files
est maintenant un type IteratorIterator
de d'itération pour Traversable
$dir
:
$files = new IteratorIterator($dir);
Comme d'habitude l'acte d'itération est effectué par le foreach
:
foreach ($files as $file) {
La sortie est exactement le même. Donc ce qui est différent? Différent est l'objet utilisé dans le foreach
. Dans le premier exemple, il est un DirectoryIterator
dans le deuxième exemple, c'est le IteratorIterator
. Cela montre la flexibilité des itérateurs: vous pouvez les remplacer les uns par les autres, le code à l'intérieur de foreach
continue de fonctionner comme prévu.
Commençons à obtenir le liste complète, y compris les sous-répertoires.
Comme nous avons maintenant spécifié le type d'itération, considérons de le changer en un autre type d'itération.
Nous savons que nous devons traverser l'arbre entier maintenant, pas seulement le premier niveau. Pour le fonctionnement avec un simple foreach
nous avons besoin d'un autre type d'itérateur: RecursiveIteratorIterator
. Et que l'on ne peut itérer que sur des objets conteneur qui ont le RecursiveIterator
interface .
L'interface est un contrat. Tout la classe l'implémentant peut être utilisée avec le RecursiveIteratorIterator
. Un exemple d'une telle classe est l'RecursiveDirectoryIterator
, qui est quelque chose comme la variante récursive de DirectoryIterator
.
Permet de voir un premier exemple de code avant d'écrire une phrase avec le mot:
$dir = new RecursiveDirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
Ce troisième exemple est presque identique au premier, mais il crée une sortie différente:
[tree]
├ tree\.
├ tree\..
├ tree\dirA
├ tree\fileA
Ok, pas si différent, le nom de fichier contient maintenant le chemin d'accès dans avant, mais le reste semble similaire aussi bien.
Comme le montre l'exemple, même l'objet directory imèle déjà l'interface RecursiveIterator
, ce n'est pas encore suffisant pour que foreach
traverse toute l'arborescence des répertoires. C'est là que le RecursiveIteratorIterator
entre en action. Exemple 4 montre comment:
$files = new RecursiveIteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
En utilisant l'objet RecursiveIteratorIterator
au lieu de l'objet $dir
précédent, foreach
parcourra tous les fichiers et répertoires de manière récursive. Cela répertorie alors tous les fichiers, comme le le type d'itération d'objet a été spécifié maintenant:
[tree]
├ tree\.
├ tree\..
├ tree\dirA\.
├ tree\dirA\..
├ tree\dirA\dirB\.
├ tree\dirA\dirB\..
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
Cela devrait déjà démontrer la différence entre la traversée à plat et la traversée en arbre. Le RecursiveIteratorIterator
est capable de traverser n'importe quelle structure arborescente en tant que liste d'éléments. Comme il y a plus d'informations (comme le niveau que l'itération prend actuellement), il est possible d'accéder à l'objet itérateur tout en itérant dessus et par exemple indenter la sortie:
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
Et sortie de exemple 5:
[tree]
├ tree\.
├ tree\..
├ tree\dirA\.
├ tree\dirA\..
├ tree\dirA\dirB\.
├ tree\dirA\dirB\..
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
Bien sûr, cela ne gagne pas un concours de beauté, mais cela montre qu'avec l'itérateur récursif, il y a plus d'informations disponibles que l'ordre linéaire de key et value. Même foreach
ne peut exprimer que ce type de linéarité, l'accès à l'itérateur lui-même permet d'obtenir plus d'informations.
Similaire à la méta-information, il existe également différentes façons de traverser l'arbre et donc d'ordonner la sortie. C'est l'en Mode de la RecursiveIteratorIterator
et il peut être réglé avec le constructeur.
L'exemple suivant indiquera au RecursiveDirectoryIterator
de supprimer les entrées de points (.
et ..
) car nous n'en avons pas besoin. Mais aussi le mode de récursion sera modifié pour prendre l'élément parent (le sous-répertoire) en premier (SELF_FIRST
) avant les enfants (les fichiers et sous-sous-répertoires dans le sous-répertoire):
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
La sortie affiche maintenant les entrées de sous-répertoire correctement répertoriées, si vous comparez avec la sortie précédente ceux-ci n'étaient pas là:
[tree]
├ tree\dirA
├ tree\dirA\dirB
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
Le mode récursivité contrôle donc quoi et quand un brach ou une feuille dans l'arbre est retourné, pour l'exemple de répertoire:
-
LEAVES_ONLY
(par défaut): liste uniquement les fichiers, Pas de répertoires. -
SELF_FIRST
(ci-dessus): liste le répertoire, puis les fichiers qui s'y trouvent. -
CHILD_FIRST
(sans exemple): Liste d'abord les fichiers dans le sous-répertoire, puis le répertoire.
Sortie de Exemple 5, avec les deux autres modes:
LEAVES_ONLY CHILD_FIRST
[tree] [tree]
├ tree\dirA\dirB\fileD ├ tree\dirA\dirB\fileD
├ tree\dirA\fileB ├ tree\dirA\dirB
├ tree\dirA\fileC ├ tree\dirA\fileB
├ tree\fileA ├ tree\dirA\fileC
├ tree\dirA
├ tree\fileA
Lorsque vous comparez cela avec la traversée standard, toutes ces choses ne sont pas disponibles. L'itération récursive est donc un peu plus complexe lorsque vous avez besoin d'envelopper votre tête autour d'elle, mais elle est facile à utiliser car elle se comporte comme un itérateur, vous le mettez dans un foreach
et fait.
Je pense que ce sont des exemples suffisants pour une réponse. Vous pouvez trouver le code source complet ainsi qu'un exemple pour afficher de beaux arbres ascii dans cet essentiel: https://gist.github.com/3599532
Faites-Le Vous-Même: Faites Le
RecursiveTreeIterator
ligne de travail par ligne.
L'exemple 5 a démontré qu'il existe des méta-informations sur l'état de l'itérateur disponible. Cependant, cela a été délibérément démontré dans l'itération foreach
. Dans la vraie vie, cela appartient naturellement à l'intérieur du RecursiveIterator
.
Meilleur exemple en est la RecursiveTreeIterator
, il prend soin de mise en retrait, préfixe et ainsi de suite. Voir le fragment de code suivant:
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Le RecursiveTreeIterator
est destiné à travailler ligne par ligne, la sortie est assez simple, avec un petit problème:
[tree]
├ tree\dirA
│ ├ tree\dirA\dirB
│ │ └ tree\dirA\dirB\fileD
│ ├ tree\dirA\fileB
│ └ tree\dirA\fileC
└ tree\fileA
Lorsqu'il est utilisé en combinaison avec un RecursiveDirectoryIterator
, Il affiche le chemin entier et pas seulement le nom de fichier. Le reste semble bon. C'est parce que les noms de fichiers sont générés par SplFileInfo
. Ceux-ci devraient être affichés comme le nom de base à la place. La sortie souhaitée est la suivant:
/// Solved ///
[tree]
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Créer un décorateur classe qui peut être utilisée avec RecursiveTreeIterator
au lieu de RecursiveDirectoryIterator
. Il devrait fournir le nom de base du courant SplFileInfo
au lieu du chemin d'accès. Le fragment de code final pourrait alors ressembler à:
$lines = new RecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Ces fragments, y compris $unicodeTreePrefix
, font partie de l'essentiel de l'annexe : Faites-Le Vous-Même: Faites Le RecursiveTreeIterator
ligne par ligne de travail..
Quelle est la différence de
IteratorIterator
etRecursiveIteratorIterator
?
Pour comprendre la différence entre ces deux itérateurs, il faut d'abord comprendre un peu les conventions de nommage utilisées et ce que nous entendons par itérateurs "récursifs".
Itérateurs récursifs et non récursifs
PHP a des itérateurs Non "récursifs", tels que ArrayIterator
et FilesystemIterator
. Il existe également des itérateurs "récursifs" tels que RecursiveArrayIterator
et RecursiveDirectoryIterator
. Ces derniers ont des méthodes leur permettant d'être percé , les anciens n'en ont pas.
Lorsque les instances de ces itérateurs sont bouclées seules, même les récursives, les valeurs ne proviennent que du niveau "supérieur" même si elles sont en boucle sur un tableau ou un répertoire imbriqué avec des sous-répertoires.
Les itérateurs récursifs implémentent le comportement récursif (via hasChildren()
, getChildren()
) mais ne l'exploitez pas.
Il pourrait être préférable de penser aux itérateurs récursifs comme des itérateurs "récursibles", ils ont le la capacité à être itérée récursivement mais simplement itérer sur une instance de l'une de ces classes ne le fera pas. Pour exploiter le comportement récursif, continuez à lire.
RecursiveIteratorIterator
C'est là que le {[3] } entre en jeu. Il a la connaissance de la façon d'appeler les itérateurs "récursibles" de manière à percer dans la structure dans une boucle normale, plate. Il met le comportement récursif en action. Il fait essentiellement le travail de pas à pas sur chacune des valeurs de l'itérateur, cherchez à voir s'il y a des "enfants" à récurer ou non, et entrez dans et hors de ces collections d'enfants. Vous collez une instance de RecursiveIteratorIterator
dans un foreach, et elle plonge dans la structure de sorte que vous n'avez pas à le faire.
Si le RecursiveIteratorIterator
n'était pas utilisé, vous devrez écrire vos propres boucles récursives pour exploiter le comportement récursif, en vérifiant par rapport à l'itérateur" récursible " hasChildren()
et en utilisant getChildren()
.
Donc c'est un bref aperçu de RecursiveIteratorIterator
, en quoi est-il différent de IteratorIterator
? Eh bien, vous posez essentiellement le même genre de question que Quelle est la différence entre un chaton et un arbre?{[53] } juste parce que les deux apparaissent dans la même Encyclopédie (ou manuel, pour les itérateurs) ne signifie pas que vous devriez être confus entre les deux.
IteratorIterator
Le travail de IteratorIterator
est de prendre n'importe quel objet Traversable
, et de l'envelopper de telle sorte qu'il satisfasse l'interface Iterator
. Une utilisation pour cela est de pouvoir ensuite appliquer un comportement spécifique à l'itérateur sur l'objet non-itérateur.
Pour donner un exemple pratique, la classe DatePeriod
est Traversable
mais pas Iterator
. En tant que tel, nous pouvons faire une boucle sur ses valeurs avec foreach()
mais ne pouvons pas faire d'autres choses que nous le ferions habituellement avec un itérateur, comme le filtrage.
Tâche : boucle sur les lundis, mercredis et vendredis des quatre prochaines semaines.
Oui, c'est trivial en foreach
- ing sur le DatePeriod
et en utilisant un if()
dans la boucle; mais ce n'est pas le but de cet exemple!
$period = new DatePeriod(new DateTime, new DateInterval('P1D'), 28);
$dates = new CallbackFilterIterator($period, function ($date) {
return in_array($date->format('l'), array('Monday', 'Wednesday', 'Friday'));
});
foreach ($dates as $date) { … }
L'extrait ci-dessus ne fonctionnera pas car CallbackFilterIterator
attend une instance d'une classe qui implémente l'interface Iterator
, ce qui n'est pas le cas de DatePeriod
. Cependant, comme il s'agit de Traversable
, nous pouvons facilement satisfaire cette exigence en utilisant IteratorIterator
.
$period = new IteratorIterator(new DatePeriod(…));
Comme vous pouvez le voir, cela n'a rien à voir avec l'itération sur les classes d'itérateurs ni la récursivité, et c'est là que réside la différence entre IteratorIterator
et RecursiveIteratorIterator
.
Résumé
RecursiveIteraratorIterator
est pour itérer sur un RecursiveIterator
(itérateur"récursible"), en exploitant le comportement récursif disponible.
IteratorIterator
est pour appliquer Iterator
comportement à non-itérateur, Traversable
objets.
RecursiveDirectoryIterator il affiche le chemin entier et pas seulement le nom de fichier. Le reste semble bon. C'est parce que les noms de fichiers sont générés par SplFileInfo. Ceux-ci devraient être affichés comme le nom de base à la place. La sortie souhaitée est la suivante:
$path =__DIR__;
$dir = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir,RecursiveIteratorIterator::SELF_FIRST);
while ($files->valid()) {
$file = $files->current();
$filename = $file->getFilename();
$deep = $files->getDepth();
$indent = str_repeat('│ ', $deep);
$files->next();
$valid = $files->valid();
if ($valid and ($files->getDepth() - 1 == $deep or $files->getDepth() == $deep)) {
echo $indent, "├ $filename\n";
} else {
echo $indent, "└ $filename\n";
}
}
Sortie:
tree
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Lorsqu'il est utilisé avec iterator_to_array()
, RecursiveIteratorIterator
parcourra récursivement le tableau pour trouver toutes les valeurs. Ce qui signifie qu'il va aplatir le tableau d'origine.
IteratorIterator
gardera la structure hiérarchique d'origine.
Cet exemple vous montrera clairement la différence:
$array = array(
'ford',
'model' => 'F150',
'color' => 'blue',
'options' => array('radio' => 'satellite')
);
$recursiveIterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
var_dump(iterator_to_array($recursiveIterator, true));
$iterator = new IteratorIterator(new ArrayIterator($array));
var_dump(iterator_to_array($iterator,true));