Como RecursiveIteratorIterator funciona em PHP?


88

Como RecursiveIteratorIteratorfunciona?

O manual do PHP não tem nada muito documentado ou explicado. Qual é a diferença entre IteratorIteratore RecursiveIteratorIterator?


2
há um exemplo em php.net/manual/en/recursiveiteratoriterator.construct.php e também há uma introdução em php.net/manual/en/class.iteratoriterator.php - você pode apontar o que exatamente você tem dificuldade de entender . O que o manual deve conter para ser mais fácil de entender?
Gordon,

1
Se você perguntar como RecursiveIteratorIteratorfunciona, já entendeu como IteratorIteratorfunciona? 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?
hakre,

@Gordon Eu não tinha certeza de como o loop foreach único pode atravessar todos os elementos na estrutura de árvore
varuog

@hakra, agora estou tentando estudar todas as interfaces incorporadas, bem como a interface spl e a implementação do iterador. Estava interessado em saber como funciona em segundo plano com o loop forach com alguns exemplos.
Varuog

@hakre Ambos são muito diferentes. IteratorIteratormapas Iteratore IteratorAggregateem um Iterator, onde REcusiveIteratorIteratoré usado para atravessar recusivly umRecursiveIterator
Adam

Respostas:


249

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 IteratorIteratorqual é um Iteratorobjeto de implementação concreto atravessando em ordem linear (e por padrão aceitando qualquer tipo de Traversableem seu construtor), o RecursiveIteratorIteratorpermite fazer um loop sobre todos os nós em uma árvore ordenada de objetos e seu construtor leva a RecursiveIterator.

Resumindo: RecursiveIteratorIteratorpermite que você faça um loop em uma árvore, IteratorIteratorpermite 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 Iteratorentão empilha internamente os diferentes programas RecursiveIteratorpor sua profundidade e mantém um ponteiro para o sub ativo atual Iteratorpara 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 foreachvocê 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 Traversalque já tenha iteração traversal recursiva - você normalmente precisa instanciar a RecursiveIteratorIteratoriteração existente ou até mesmo escrever uma iteração traversal recursiva que seja Traversablesua 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:

  • Enquanto IteratorIteratorleva any Traversablepara travessia linear, RecursiveIteratorIteratorprecisa de um RecursiveIteratorloop mais específico para uma árvore.
  • Onde IteratorIteratorexpõe sua Iteratorvia principal getInnerIerator(), RecursiveIteratorIteratorfornece a sub-rotina ativa atual Iteratorapenas por meio desse método.
  • Embora não IteratorIteratoresteja totalmente ciente de nada como pai ou filhos, RecursiveIteratorIteratorsabe como obter e atravessar os filhos também.
  • IteratorIteratornão precisa de uma pilha de iteradores, RecursiveIteratorIteratortem essa pilha e conhece o sub-iterador ativo.
  • Onde IteratorIteratortem sua ordem devido à linearidade e nenhuma escolha, RecursiveIteratorIteratortem uma escolha para travessia adicional e precisa decidir por cada nó (decidido por modo porRecursiveIteratorIterator ).
  • RecursiveIteratorIteratortem 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 Traversableque é possível por meio de Iteratorou 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:

Árvore de Diretório

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 IteratorIteratorqual não faz recursão para percorrer a árvore de diretórios. E o RecursiveIteratorIteratorque pode penetrar na árvore, conforme mostra a lista Recursiva.

No primeiro um exemplo muito básico com um DirectoryIteratorque implementa Traversableo que permite foreacha 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 IteratorIteratorou RecursiveIteratorIterator. Em vez disso, basta apenas usar o foreachque opera na Traversableinterface.

Como, foreachpor 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 RecursiveIteratorIteratormais visível posteriormente), vamos especificar o tipo linear de iteração especificando explicitamente o IteratorIteratortipo 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 $filesagora é um IteratorIteratortipo 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, DirectoryIteratorno segundo exemplo é o IteratorIterator. Isso mostra a flexibilidade que os iteradores têm: você pode substituí-los uns pelos outros, o código interno foreachapenas 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 foreachprecisamos de um tipo diferente de iterator: RecursiveIteratorIterator. E isso só pode ser iterado em objetos de contêiner que possuem a RecursiveIteratorinterface .

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 RecursiveIteratorinterface, isso ainda não é suficiente para fazer foreachpercorrer toda a árvore de diretório. É aqui que o RecursiveIteratorIteratorentra 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 RecursiveIteratorIteratorvez de apenas o $dirobjeto anterior fará foreachcom 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 foreachsó 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 RecursiveDirectoryIteratorpara 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 foreache 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 RecursiveTreeIteratortrabalho 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 foreachiteraçã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 RecursiveTreeIteratorobjetivo é 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 RecursiveTreeIteratorvez de RecursiveDirectoryIterator. Ele deve fornecer o nome de base do atual em SplFileInfovez 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, $unicodeTreePrefixsão parte da essência do Apêndice: Faça você mesmo: faça o RecursiveTreeIteratortrabalho linha por linha. .


Ele não responde às perguntas feitas, contém erros factuais e perde pontos principais quando você passa a personalizar a iteração. Em suma, parece uma tentativa pobre de obter recompensa por um tópico sobre o qual você não sabe muito ou, se sabe, não pode destilar em uma resposta às perguntas feitas.
salathe

2
Bem, o que não responde à minha pergunta "por que", você apenas faz mais palavras sem dizer muito. Talvez você comece realmente com um erro que conta? Aponte para isso, não mantenha isso em segredo.
hakre,

A primeira frase está incorreta: "RecursiveIteratorIterator é um IteratorIterator que suporta ...", isso não é verdade.
Salathe 02 de

1
@salathe: Obrigado por seu feedback. Eu editei a resposta para abordá-la. A primeira frase de fato estava errada e incompleta. Ainda deixei de fora os detalhes de implementação concretos RecursiveIteratorIteratorporque 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.
hakre,

1
A primeira parte foi melhorada um pouco, mas uma vez que você começa a passar para os exemplos, ela ainda apresenta imprecisões factuais. Se você cortar a resposta na régua horizontal, será muito melhorado.
salathe

31

Qual é a diferença de IteratorIteratore RecursiveIteratorIterator?

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".

Iteradores recursivos e não recursivos

O PHP possui iteradores não "recursivos", como ArrayIteratore FilesystemIterator. Existem também iteradores "recursivos", como o RecursiveArrayIteratore 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.

RecursiveIteratorIterator

É aqui que RecursiveIteratorIteratorentra 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 RecursiveIteratorIteratorem um foreach, e ele mergulha na estrutura para que você não precise fazer isso.

Se o RecursiveIteratorIteratornã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 RecursiveIteratorIteratorcomo é 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.

IteratorIterator

O trabalho do IteratorIteratoré pegar qualquer Traversableobjeto e envolvê-lo de forma que satisfaça a Iteratorinterface. 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 DatePeriodclasse é, Traversablemas 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 DatePeriode 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 CallbackFilterIteratorespera uma instância de uma classe que implementa a Iteratorinterface, que DatePeriodnã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 IteratorIteratore RecursiveIteratorIterator.

Resumo

RecursiveIteraratorIteratoré para iterar sobre um RecursiveIterator(iterador "recursível"), explorando o comportamento recursivo que está disponível.

IteratorIteratoré para aplicar o Iteratorcomportamento a Traversableobjetos não iterativos .


Não é IteratorIteratorapenas o tipo padrão de passagem de ordem linear para Traversableobjetos? Aqueles que poderiam ser usados ​​sem ele exatamente foreachcomo estão? E, ainda mais, não é RecursiveIterator sempre um Traversablee, portanto, não apenas, IteratorIteratormas também RecursiveIteratorIteratorsempre "para aplicar Iteratorcomportamento a objetos não iteradores, Traversable" ? (Agora eu diria que foreachaplica 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)
hakre

Como minha resposta afirma, IteratorIteratoré uma classe que trata de envolver Traversableobjetos em um Iterator. Mais nada . Você parece estar aplicando o termo de forma mais geral.
salathe

Resposta aparentemente informativa. Uma pergunta: RecursiveIteratorIterator também não agruparia objetos para que eles também tivessem acesso ao comportamento de Iterator? A única diferença entre os dois seria que o RecursiveIteratorIterator pode fazer drill down, enquanto o IteratorIterator não pode?
Mike Purcell

@salathe, você sabe por que o iterador recursivo (RecursiveDirectoryIterator) não implementa o comportamento hasChildren (), getChildren ()?
anru,

7
1 para dizer "recursível". O nome me desviou por muito tempo porque o Recursivein RecursiveIteratorimplica comportamento, enquanto um nome mais adequado teria sido aquele que descreve capacidade, como RecursibleIterator.
cabra

0

Quando usado com iterator_to_array(), RecursiveIteratorIteratorpercorrerá 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));

Isso é totalmente enganoso. new IteratorIterator(new ArrayIterator($array))equivale a new ArrayIterator($array), isto é, o externo não IteratorIteratorestá 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 RecursiveArrayIteratorpercorre seu iterador interno.
Quolonel Perguntas

0

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.