Como RecursiveIteratorIterator
funciona?
O manual do PHP não tem nada muito documentado ou explicado. Qual é a diferença entre IteratorIterator
e RecursiveIteratorIterator
?
Como RecursiveIteratorIterator
funciona?
O manual do PHP não tem nada muito documentado ou explicado. Qual é a diferença entre IteratorIterator
e RecursiveIteratorIterator
?
RecursiveIteratorIterator
funciona, já entendeu como IteratorIterator
funciona? Quer dizer, é basicamente o mesmo, apenas a interface que é consumida pelos dois é diferente. E você está mais interessado em alguns exemplos ou deseja ver a diferença da implementação do código C subjacente?
IteratorIterator
mapas Iterator
e IteratorAggregate
em um Iterator
, onde REcusiveIteratorIterator
é usado para atravessar recusivly umRecursiveIterator
Respostas:
RecursiveIteratorIterator
é uma travessia de árvore deIterator
implementação concreta . Ele permite que um programador atravesse um objeto contêiner que implementa a interface, consulte Iterator na Wikipedia para os princípios gerais, tipos, semânticas e padrões de iteradores.RecursiveIterator
Diferentemente de IteratorIterator
qual é um Iterator
objeto de implementação concreto atravessando em ordem linear (e por padrão aceitando qualquer tipo de Traversable
em seu construtor), o RecursiveIteratorIterator
permite fazer um loop sobre todos os nós em uma árvore ordenada de objetos e seu construtor leva a RecursiveIterator
.
Resumindo: RecursiveIteratorIterator
permite que você faça um loop em uma árvore, IteratorIterator
permite que você faça um loop em uma lista. Eu mostro isso com alguns exemplos de código abaixo em breve.
Tecnicamente, isso funciona quebrando a linearidade ao percorrer todos os filhos de um nó (se houver). Isso é possível porque, por definição, todos os filhos de um nó são novamente a RecursiveIterator
. O nível superior Iterator
então empilha internamente os diferentes programas RecursiveIterator
por sua profundidade e mantém um ponteiro para o sub ativo atual Iterator
para travessia.
Isso permite visitar todos os nós de uma árvore.
Os princípios básicos são os mesmos de IteratorIterator
: Uma interface especifica o tipo de iteração e a classe base do iterador é a implementação dessa semântica. Compare com os exemplos abaixo, para loop linear com foreach
você normalmente não pense muito sobre os detalhes de implementação, a menos que você precise definir um novo Iterator
(por exemplo, quando algum tipo concreto não implementa Traversable
).
Para traversal recursivo - a menos que você não use um pré-definido Traversal
que já tenha iteração traversal recursiva - você normalmente precisa instanciar a RecursiveIteratorIterator
iteração existente ou até mesmo escrever uma iteração traversal recursiva que seja Traversable
sua para ter este tipo de iteração traversal foreach
.
Dica: Você provavelmente não implementou nem um nem outro por conta própria, então isso pode valer a pena fazer por sua experiência prática das diferenças que eles têm. Você encontra uma sugestão de DIY no final da resposta.
Em resumo, diferenças técnicas:
IteratorIterator
leva any Traversable
para travessia linear, RecursiveIteratorIterator
precisa de um RecursiveIterator
loop mais específico para uma árvore.IteratorIterator
expõe sua Iterator
via principal getInnerIerator()
, RecursiveIteratorIterator
fornece a sub-rotina ativa atual Iterator
apenas por meio desse método.IteratorIterator
esteja totalmente ciente de nada como pai ou filhos, RecursiveIteratorIterator
sabe como obter e atravessar os filhos também.IteratorIterator
não precisa de uma pilha de iteradores, RecursiveIteratorIterator
tem essa pilha e conhece o sub-iterador ativo.IteratorIterator
tem sua ordem devido à linearidade e nenhuma escolha, RecursiveIteratorIterator
tem uma escolha para travessia adicional e precisa decidir por cada nó (decidido por modo porRecursiveIteratorIterator
).RecursiveIteratorIterator
tem mais métodos do que IteratorIterator
.Para resumir: RecursiveIterator
é um tipo concreto de iteração (looping sobre uma árvore) que funciona em seus próprios iteradores, a saber RecursiveIterator
. Esse é o mesmo princípio subjacente de IteratorIerator
, mas o tipo de iteração é diferente (ordem linear).
Idealmente, você também pode criar seu próprio conjunto. A única coisa necessária é que seu iterador implemente o Traversable
que é possível por meio de Iterator
ou IteratorAggregate
. Então você pode usá-lo com foreach
. Por exemplo, algum tipo de objeto de iteração recursiva de travessia de árvore ternária junto com a interface de iteração correspondente para o (s) objeto (s) recipiente.
Vamos revisar alguns exemplos da vida real que não são tão abstratos. Entre interfaces, iteradores concretos, objetos de contêiner e semântica de iteração, talvez não seja uma ideia tão ruim.
Pegue uma lista de diretórios como exemplo. Considere que você tem o seguinte arquivo e árvore de diretório no disco:
Enquanto um iterador com ordem linear apenas atravessa a pasta e os arquivos de nível superior (uma única listagem de diretório), o iterador recursivo também percorre as subpastas e lista todas as pastas e arquivos (uma listagem de diretório com listagens de seus subdiretórios):
Non-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Você pode facilmente comparar isso com o IteratorIterator
qual não faz recursão para percorrer a árvore de diretórios. E o RecursiveIteratorIterator
que pode penetrar na árvore, conforme mostra a lista Recursiva.
No primeiro um exemplo muito básico com um DirectoryIterator
que implementa Traversable
o que permite foreach
a iterate sobre ele:
$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
O exemplo de saída para a estrutura de diretório acima é:
[tree]
├ .
├ ..
├ dirA
├ fileA
Como você pode ver, isso ainda não está usando IteratorIterator
ou RecursiveIteratorIterator
. Em vez disso, basta apenas usar o foreach
que opera na Traversable
interface.
Como, foreach
por padrão, só conhece o tipo de iteração chamada ordem linear, podemos querer especificar o tipo de iteração explicitamente. À primeira vista pode parecer muito prolixo, mas para fins de demonstração (e para fazer a diferença com RecursiveIteratorIterator
mais visível posteriormente), vamos especificar o tipo linear de iteração especificando explicitamente o IteratorIterator
tipo de iteração para a listagem de diretório:
$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Este exemplo é quase idêntico ao primeiro, a diferença é que $files
agora é um IteratorIterator
tipo de iteração para Traversable
$dir
:
$files = new IteratorIterator($dir);
Como de costume, o ato de iteração é realizado por foreach
:
foreach ($files as $file) {
A saída é exatamente a mesma. Então, o que é diferente? Diferente é o objeto usado no foreach
. No primeiro exemplo é um, DirectoryIterator
no segundo exemplo é o IteratorIterator
. Isso mostra a flexibilidade que os iteradores têm: você pode substituí-los uns pelos outros, o código interno foreach
apenas continua a funcionar conforme o esperado.
Vamos começar a obter a lista completa, incluindo subdiretórios.
Como agora especificamos o tipo de iteração, vamos considerar alterá-lo para outro tipo de iteração.
Sabemos que precisamos atravessar a árvore inteira agora, não apenas o primeiro nível. Para ter esse trabalho com um simples foreach
precisamos de um tipo diferente de iterator: RecursiveIteratorIterator
. E isso só pode ser iterado em objetos de contêiner que possuem a RecursiveIterator
interface .
A interface é um contrato. Qualquer classe que o implemente pode ser usada junto com o RecursiveIteratorIterator
. Um exemplo dessa classe é o RecursiveDirectoryIterator
, que é algo como a variante recursiva de DirectoryIterator
.
Vamos ver um primeiro exemplo de código antes de escrever qualquer outra frase com a palavra I:
$dir = new RecursiveDirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
Este terceiro exemplo é quase idêntico ao primeiro, no entanto, cria algumas saídas diferentes:
[tree]
├ tree\.
├ tree\..
├ tree\dirA
├ tree\fileA
Ok, não tão diferente, o nome do arquivo agora contém o nome do caminho na frente, mas o resto também parece semelhante.
Como mostra o exemplo, mesmo o objeto diretório já implementa a RecursiveIterator
interface, isso ainda não é suficiente para fazer foreach
percorrer toda a árvore de diretório. É aqui que o RecursiveIteratorIterator
entra em ação. O Exemplo 4 mostra como:
$files = new RecursiveIteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Usar o em RecursiveIteratorIterator
vez de apenas o $dir
objeto anterior fará foreach
com que percorra todos os arquivos e diretórios de maneira recursiva. Em seguida, isso lista todos os arquivos, pois o tipo de iteração do objeto foi especificado agora:
[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
Isso já deve demonstrar a diferença entre a travessia plana e em árvore. O RecursiveIteratorIterator
é capaz de percorrer qualquer estrutura semelhante a uma árvore como uma lista de elementos. Como há mais informações (como o nível em que a iteração ocorre atualmente), é possível acessar o objeto iterador ao iterar sobre ele e, por exemplo, indentar a saída:
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
E o resultado do Exemplo 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
Claro, isso não ganha um concurso de beleza, mas mostra que, com o iterador recursivo, há mais informações disponíveis do que apenas a ordem linear de chave e valor . A Even foreach
só pode expressar esse tipo de linearidade, acessando o próprio iterador permite obter mais informações.
Semelhante à meta-informação, também existem diferentes maneiras possíveis de percorrer a árvore e, portanto, ordenar a saída. Este é o modo deRecursiveIteratorIterator
e pode ser definido com o construtor.
O próximo exemplo dirá ao RecursiveDirectoryIterator
para remover as entradas de pontos ( .
e ..
), pois não precisamos delas. Mas também o modo de recursão será alterado para levar o elemento pai (o subdiretório) primeiro ( SELF_FIRST
) antes dos filhos (os arquivos e sub-subdiretórios no subdiretório):
$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";
}
A saída agora mostra as entradas do subdiretório listadas corretamente, se você comparar com a saída anterior, aquelas não estavam lá:
[tree]
├ tree\dirA
├ tree\dirA\dirB
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
O modo de recursão, portanto, controla o que e quando um brach ou folha na árvore é retornado, para o exemplo de diretório:
LEAVES_ONLY
(padrão): Listar apenas arquivos, sem diretórios.SELF_FIRST
(acima): lista o diretório e os arquivos nele contidos.CHILD_FIRST
(sem exemplo): Liste os arquivos no subdiretório primeiro e, em seguida, no diretório.Saída do Exemplo 5 com os outros dois modos:
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
Quando você compara isso com o percurso padrão, todas essas coisas não estão disponíveis. A iteração recursiva, portanto, é um pouco mais complexa quando você precisa envolvê-la em sua cabeça, no entanto, é fácil de usar porque se comporta como um iterador, você a coloca em um foreach
e pronto.
Acho que esses são exemplos suficientes para uma resposta. Você pode encontrar o código-fonte completo, bem como um exemplo para exibir árvores ascii de boa aparência nesta essência: https://gist.github.com/3599532
Faça você mesmo: faça o
RecursiveTreeIterator
trabalho linha por linha.
O Exemplo 5 demonstrou que há meta-informações disponíveis sobre o estado do iterador. No entanto, esta foi propositadamente demonstrado dentro da foreach
iteração. Na vida real, isso pertence naturalmente ao RecursiveIterator
.
Um exemplo melhor é o RecursiveTreeIterator
, ele cuida de indentação, prefixação e assim por diante. Veja o seguinte fragmento de código:
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
O RecursiveTreeIterator
objetivo é trabalhar linha por linha, a saída é bastante direta com um pequeno problema:
[tree]
├ tree\dirA
│ ├ tree\dirA\dirB
│ │ └ tree\dirA\dirB\fileD
│ ├ tree\dirA\fileB
│ └ tree\dirA\fileC
└ tree\fileA
Quando usado em combinação com a RecursiveDirectoryIterator
, exibe o nome do caminho completo e não apenas o nome do arquivo. O resto parece bom. Isso ocorre porque os nomes dos arquivos são gerados por SplFileInfo
. Esses devem ser exibidos como o nome de base. O resultado desejado é o seguinte:
/// Solved ///
[tree]
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Crie uma classe de decorador que pode ser usada com em RecursiveTreeIterator
vez de RecursiveDirectoryIterator
. Ele deve fornecer o nome de base do atual em SplFileInfo
vez do nome do caminho. O fragmento de código final poderia ser assim:
$lines = new RecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Esses fragmentos, inclusive, $unicodeTreePrefix
são parte da essência do Apêndice: Faça você mesmo: faça o RecursiveTreeIterator
trabalho linha por linha. .
RecursiveIteratorIterator
porque isso é comum com outros tipos, mas dei algumas informações técnicas de como ele realmente funciona. Acho que os exemplos mostram bem as diferenças: o tipo de iteração é a principal diferença entre os dois. Não tenho ideia se você comprar o tipo de iteração que você inventa de uma forma um pouco diferente, mas IMHO não é fácil com os tipos de semântica de iteração.
Qual é a diferença de
IteratorIterator
eRecursiveIteratorIterator
?
Para entender a diferença entre esses dois iteradores, deve-se primeiro entender um pouco sobre as convenções de nomenclatura usadas e o que queremos dizer com iteradores "recursivos".
O PHP possui iteradores não "recursivos", como ArrayIterator
e FilesystemIterator
. Existem também iteradores "recursivos", como o RecursiveArrayIterator
e RecursiveDirectoryIterator
. Os últimos têm métodos que os permitem aprofundar, os primeiros não.
Quando as instâncias desses iteradores são executadas em loop por conta própria, mesmo os recursivos, os valores só vêm do nível "superior", mesmo que estejam em loop em uma matriz ou diretório aninhado com subdiretórios.
Os iteradores recursivos implementam comportamento recursivo (via hasChildren()
, getChildren()
) , mas não o exploram .
Pode ser melhor pensar nos iteradores recursivos como iteradores "recursíveis", eles têm a capacidade de serem iterados recursivamente, mas simplesmente iterar sobre uma instância de uma dessas classes não fará isso. Para explorar o comportamento recursivo, continue lendo.
É aqui que RecursiveIteratorIterator
entra o jogo. Ele tem o conhecimento de como chamar os iteradores "recursíveis" de forma a detalhar a estrutura em um loop normal e plano. Ele coloca o comportamento recursivo em ação. Essencialmente, ele faz o trabalho de passar por cima de cada um dos valores no iterador, procurando ver se há "filhos" para recursar ou não, e entrar e sair dessas coleções de filhos. Você coloca uma instância de RecursiveIteratorIterator
em um foreach, e ele mergulha na estrutura para que você não precise fazer isso.
Se o RecursiveIteratorIterator
não foi usado, você teria que escrever seus próprios loops recursivos para explorar o comportamento recursivo, comparando com o iterador "recursível" hasChildren()
e usando getChildren()
.
Essa é uma breve visão geral de RecursiveIteratorIterator
como é diferente IteratorIterator
? Bem, você está basicamente fazendo o mesmo tipo de pergunta que Qual é a diferença entre um gatinho e uma árvore? Só porque ambos aparecem na mesma enciclopédia (ou manual, para os iteradores) não significa que você deva se confundir entre os dois.
O trabalho do IteratorIterator
é pegar qualquer Traversable
objeto e envolvê-lo de forma que satisfaça a Iterator
interface. Um uso para isso é ser capaz de aplicar o comportamento específico do iterador no objeto não iterador.
Para dar um exemplo prático, a DatePeriod
classe é, Traversable
mas não um Iterator
. Como tal, podemos fazer um loop sobre seus valores com, foreach()
mas não podemos fazer outras coisas que normalmente faríamos com um iterador, como a filtragem.
TAREFA : Loop ao longo das segundas, quartas e sextas-feiras das próximas quatro semanas.
Sim, isso é trivial por foreach
-ing no DatePeriod
e usando um if()
dentro do loop; mas esse não é o ponto deste exemplo!
$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) { … }
O trecho acima não funcionará porque o CallbackFilterIterator
espera uma instância de uma classe que implementa a Iterator
interface, que DatePeriod
não. No entanto, como é Traversable
, podemos facilmente satisfazer esse requisito usando IteratorIterator
.
$period = new IteratorIterator(new DatePeriod(…));
Como você pode ver, isso não tem nada a ver com a iteração em classes de iteradores nem com a recursão, e é aí que reside a diferença entre IteratorIterator
e RecursiveIteratorIterator
.
RecursiveIteraratorIterator
é para iterar sobre um RecursiveIterator
(iterador "recursível"), explorando o comportamento recursivo que está disponível.
IteratorIterator
é para aplicar o Iterator
comportamento a Traversable
objetos não iterativos .
IteratorIterator
apenas o tipo padrão de passagem de ordem linear para Traversable
objetos? Aqueles que poderiam ser usados sem ele exatamente foreach
como estão? E, ainda mais, não é RecursiveIterator
sempre um Traversable
e, portanto, não apenas, IteratorIterator
mas também RecursiveIteratorIterator
sempre "para aplicar Iterator
comportamento a objetos não iteradores, Traversable" ? (Agora eu diria que foreach
aplica o tipo de iteração por meio do objeto iterador em objetos contêiner que implementam uma interface do tipo iterador, portanto, esses são objetos contêiner-iterador, sempre Traversable
)
IteratorIterator
é uma classe que trata de envolver Traversable
objetos em um Iterator
. Mais nada . Você parece estar aplicando o termo de forma mais geral.
Recursive
in RecursiveIterator
implica comportamento, enquanto um nome mais adequado teria sido aquele que descreve capacidade, como RecursibleIterator
.
Quando usado com iterator_to_array()
, RecursiveIteratorIterator
percorrerá recursivamente o array para encontrar todos os valores. O que significa que ele irá nivelar a matriz original.
IteratorIterator
manterá a estrutura hierárquica original.
Este exemplo mostrará claramente a diferença:
$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));
new IteratorIterator(new ArrayIterator($array))
equivale a new ArrayIterator($array)
, isto é, o externo não IteratorIterator
está fazendo nada. Além disso, o achatamento da saída não tem nada a ver com iterator_to_array
- ele simplesmente converte o iterador em uma matriz. O achatamento é uma propriedade da maneira como RecursiveArrayIterator
percorre seu iterador interno.
RecursiveDirectoryIterator ele exibe o nome do caminho completo e não apenas o nome do arquivo. O resto parece bom. Isso ocorre porque os nomes dos arquivos são gerados por SplFileInfo. Em vez disso, eles devem ser exibidos como o nome de base. O resultado desejado é o seguinte:
$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";
}
}
resultado:
tree
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA