mirror of
https://github.com/php/doc-pt_br.git
synced 2026-03-24 07:02:09 +01:00
606 lines
15 KiB
XML
606 lines
15 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!-- EN-Revision: 08e58ace7e5b538c8ed75d784a54885d5f785d30 Maintainer: ae Status: ready --><!-- CREDITS: royopa,ae,fabioluciano,lhsazevedo,leonardolara -->
|
|
<chapter xml:id="language.generators" xmlns="http://docbook.org/ns/docbook">
|
|
<title>Geradores</title>
|
|
|
|
<sect1 xml:id="language.generators.overview">
|
|
<title>Geradores, visão geral</title>
|
|
<?phpdoc print-version-for="generators"?>
|
|
|
|
<para>
|
|
Os geradores fornecem uma maneira fácil de implementar
|
|
<link linkend="language.oop5.iterations">iteradores</link> simples sem a
|
|
sobrecarga ou complexidade de criar uma classe que implemente
|
|
a interface <classname>Iterator</classname>.
|
|
</para>
|
|
|
|
<para>
|
|
Um gerador oferece uma maneira conveniente de fornecer dados a loops &foreach;
|
|
sem ter que construir um array na memória antecipadamente, o que pode fazer com que o programa
|
|
exceda um limite de memória ou exija uma quantidade considerável de
|
|
tempo de processamento para essa geração. Em vez disso, uma função geradora pode ser usada,
|
|
que é o mesmo que uma
|
|
<link linkend="functions.user-defined">função</link> normal, exceto que
|
|
ao invés do
|
|
<link linkend="functions.returning-values">retorno</link> ocorrer uma única vez, um
|
|
gerador pode executar &yield; quantas vezes forem necessárias para fornecer os
|
|
valores a serem iterados.
|
|
Tal como acontece com os iteradores, o acesso aleatório aos dados não é possível.
|
|
</para>
|
|
|
|
<para>
|
|
Um exemplo simples para isso é reimplementar a função
|
|
<function>range</function>. A função <function>range</function> padrão
|
|
tem que gerar um array com cada valor dentro dele e retorná-lo, o que pode
|
|
resultar em grandes arrays: por exemplo, chamar
|
|
<command>range(0, 1000000)</command> irá resultar numa utilização de memória
|
|
de mais de 100 MB.
|
|
</para>
|
|
|
|
<para>
|
|
Como alternativa, nós podemos implementar um gerador
|
|
<literal>xrange()</literal>, que só precisará de memória suficiente para
|
|
criar um objeto <classname>Iterator</classname> e acompanhar o estado atual
|
|
do gerador internamente, que utiliza menos de 1 kilobyte de memória.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Implementando <function>range</function> como um gerador</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function xrange($start, $limit, $step = 1) {
|
|
if ($start <= $limit) {
|
|
if ($step <= 0) {
|
|
throw new LogicException('O passo precisa ser positivo');
|
|
}
|
|
|
|
for ($i = $start; $i <= $limit; $i += $step) {
|
|
yield $i;
|
|
}
|
|
} else {
|
|
if ($step >= 0) {
|
|
throw new LogicException('O passo precisa ser negativo');
|
|
}
|
|
|
|
for ($i = $start; $i >= $limit; $i += $step) {
|
|
yield $i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Observe abaixo que ambos range() e xrange() tem a mesma
|
|
* saída.
|
|
*/
|
|
|
|
echo 'Números ímpares a partir de range(): ';
|
|
foreach (range(1, 9, 2) as $number) {
|
|
echo "$number ";
|
|
}
|
|
echo "\n";
|
|
|
|
echo 'Números ímpares a partir de xrange(): ';
|
|
foreach (xrange(1, 9, 2) as $number) {
|
|
echo "$number ";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
Números ímpares a partir de range(): 1 3 5 7 9
|
|
Números ímpares a partir de xrange(): 1 3 5 7 9
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
|
|
<sect2 xml:id="language.generators.object">
|
|
<title>Objetos <classname>Generator</classname></title>
|
|
<para>
|
|
Quando uma função geradora é chamada, um objeto interno
|
|
de tipo <classname>Generator</classname> é retornado. Este objeto
|
|
implementa a interface <classname>Iterator</classname> que um objeto
|
|
somente leitura de uma direção implementaria, provendo também métodos que
|
|
podem ser utilizados para manipular o estado do gerador, incluindo enviar
|
|
e retornar valores.
|
|
</para>
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.generators.syntax">
|
|
<title>Sintaxe do Gerador</title>
|
|
|
|
<para>
|
|
Uma função geradora se parece com uma função normal, exceto que ao invés
|
|
de retornar um valor, um gerador pode executar &yield; quantas vezes forem necessárias.
|
|
Qualquer função que contenha &yield; é uma função geradora.
|
|
</para>
|
|
|
|
<para>
|
|
Quando uma função geradora é chamada, ela retorna um objeto que pode ser
|
|
iterado. Quando você itera através desse objeto (por exemplo, por um loop
|
|
&foreach;), o PHP irá chamar os métodos de iteração do objeto toda vez que precisar de
|
|
um valor, em seguida salva o estado do gerador quando o valor é produzido,
|
|
de modo que possa ser retomado quando o próximo valor for necessário.
|
|
</para>
|
|
|
|
<para>
|
|
Quando não houver mais valores a serem produzidos, o gerador pode
|
|
simplesmente retornar, e o código que chamou continua como se um array tivesse
|
|
ficado sem valores.
|
|
</para>
|
|
|
|
<note>
|
|
<para>
|
|
Um gerador pode retornar valores, que podem ser recuperados usando
|
|
<methodname>Generator::getReturn</methodname>.
|
|
</para>
|
|
</note>
|
|
|
|
<sect2 xml:id="control-structures.yield">
|
|
<title>A palavra-chave <command>yield</command></title>
|
|
|
|
<para>
|
|
O coração de uma função geradora é a palavra-chave <command>yield</command>.
|
|
Na sua forma mais simples, uma declaração yield se parece muito com um
|
|
retorno, exceto que em vez de parar a execução da função e retornar, o
|
|
yield fornece um valor para o código de loop sobre o gerador e pausa a
|
|
execução da função do gerador.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Um exemplo simples de valores yield</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function gen_one_to_three() {
|
|
for ($i = 1; $i <= 3; $i++) {
|
|
// Observe que $i é preservado entre chamadas.
|
|
yield $i;
|
|
}
|
|
}
|
|
|
|
$generator = gen_one_to_three();
|
|
foreach ($generator as $value) {
|
|
echo "$value\n";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1
|
|
2
|
|
3
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
|
|
<note>
|
|
<para>
|
|
Internamente, chaves inteiras sequenciais serão pareadas com os valores
|
|
entregues, assim como um array não associativo.
|
|
</para>
|
|
</note>
|
|
|
|
<sect3 xml:id="control-structures.yield.associative">
|
|
<title>Produzindo valores com chaves</title>
|
|
|
|
<para>
|
|
O PHP suporta arrays associativos e geradores não são diferentes. Além
|
|
de produzir valores simples, como mostrado acima, você também pode produzir
|
|
uma chave ao mesmo tempo.
|
|
</para>
|
|
|
|
<para>
|
|
A sintaxe para preparar um par de chave/valor é muito semelhante ao
|
|
utilizado para definir um array associativo, como mostrado abaixo.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Produzindo um par de chave/valor</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
/*
|
|
* A saída são valores separados por ponto e vírgula, com o primeiro
|
|
* valor sendo usado como um id ou chave.
|
|
*/
|
|
|
|
$input = <<<'EOF'
|
|
1;PHP;Likes dollar signs
|
|
2;Python;Likes whitespace
|
|
3;Ruby;Likes blocks
|
|
EOF;
|
|
|
|
function input_parser($input) {
|
|
foreach (explode("\n", $input) as $line) {
|
|
$fields = explode(';', $line);
|
|
$id = array_shift($fields);
|
|
|
|
yield $id => $fields;
|
|
}
|
|
}
|
|
|
|
foreach (input_parser($input) as $id => $fields) {
|
|
echo "$id:\n";
|
|
echo " $fields[0]\n";
|
|
echo " $fields[1]\n";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1:
|
|
PHP
|
|
Likes dollar signs
|
|
2:
|
|
Python
|
|
Likes whitespace
|
|
3:
|
|
Ruby
|
|
Likes blocks
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
|
|
<sect3 xml:id="control-structures.yield.null">
|
|
<title>Produzindo valores nulos</title>
|
|
|
|
<para>
|
|
O yield pode ser chamado sem um argumento para produzir um valor &null;
|
|
com uma chave automática.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Produzindo valores &null;</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function gen_three_nulls() {
|
|
foreach (range(1, 3) as $i) {
|
|
yield;
|
|
}
|
|
}
|
|
|
|
var_dump(iterator_to_array(gen_three_nulls()));
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
array(3) {
|
|
[0]=>
|
|
NULL
|
|
[1]=>
|
|
NULL
|
|
[2]=>
|
|
NULL
|
|
}
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
|
|
<sect3 xml:id="control-structures.yield.references">
|
|
<title>Produzindo valores por referência</title>
|
|
|
|
<para>
|
|
Funções geradoras são capazes de produzir valores por referência bem como
|
|
por valor. Isso é feito da mesma forma que
|
|
<link linkend="functions.returning-values">retornar referências de funções</link>:
|
|
incluindo um & no início do nome da função.
|
|
</para>
|
|
|
|
<example>
|
|
<title>Produzindo valores por referência</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function &gen_reference() {
|
|
$value = 3;
|
|
|
|
while ($value > 0) {
|
|
yield $value;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Observe que podemos mudar $number dentro do loop, e
|
|
* porque o gerador retorna referências, $value
|
|
* dentro de gen_reference() é modificado.
|
|
*/
|
|
foreach (gen_reference() as &$number) {
|
|
echo (--$number).'... ';
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
2... 1... 0...
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
|
|
<sect3 xml:id="control-structures.yield.from">
|
|
<title>Delegação de gerador via <command>yield from</command></title>
|
|
|
|
<para>
|
|
A delegação de gerador permite retornar valores de outro
|
|
gerador, objeto <classname>Traversable</classname> ou um
|
|
<type>array</type> utilizando para isso a instrução <command>yield from</command>.
|
|
O gerador externo retornará todos os valores do gerador interno,
|
|
objeto ou array até que o mesmo não seja mais válido, a partir de onde a execução
|
|
continuará no gerador externo.
|
|
</para>
|
|
|
|
<para>
|
|
Se um gerador é utilizado com <command>yield from</command>, a expressão
|
|
<command>yield from</command> também retornará qualquer valor
|
|
retornado pelo gerador interno.
|
|
</para>
|
|
|
|
<caution>
|
|
<title>Armazenando em um array (por exemplo, com <function>iterator_to_array</function>)</title>
|
|
|
|
<para>
|
|
<command>yield from</command> não redefine as chaves. Ele preserva
|
|
as chaves retornadas pelo objeto <classname>Traversable</classname>, ou
|
|
<type>array</type>. Deste modo, alguns valores podem compartilhar uma chave em comum com outro
|
|
<command>yield</command> ou <command>yield from</command>, o qual, após
|
|
inserido em um array, irá sobrescrever os valores anteriores com essa chave.
|
|
</para>
|
|
|
|
<para>
|
|
Um caso onde isso importa é a função <function>iterator_to_array</function>
|
|
retornando um array com chaves por padrão, levando a resultados possivelmente inesperados.
|
|
<function>iterator_to_array</function> possui um segundo parâmetro
|
|
<parameter>preserve_keys</parameter> que pode ser definido como &false; para coletar
|
|
todos os valores enquanto ignora as chaves retornadas pelo <classname>Generator</classname>.
|
|
</para>
|
|
|
|
<example>
|
|
<title><command>yield from</command> com <function>iterator_to_array</function></title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function inner() {
|
|
yield 1; // chave 0
|
|
yield 2; // chave 1
|
|
yield 3; // chave 2
|
|
}
|
|
function gen() {
|
|
yield 0; // chave 0
|
|
yield from inner(); // chaves 0-2
|
|
yield 4; // chave 1
|
|
}
|
|
// Passe false no segundo parâmetro para obter um array [0, 1, 2, 3, 4]
|
|
var_dump(iterator_to_array(gen()));
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
array(3) {
|
|
[0]=>
|
|
int(1)
|
|
[1]=>
|
|
int(4)
|
|
[2]=>
|
|
int(3)
|
|
}
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</caution>
|
|
|
|
<example>
|
|
<title>Uso básico de <command>yield from</command></title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function count_to_ten() {
|
|
yield 1;
|
|
yield 2;
|
|
yield from [3, 4];
|
|
yield from new ArrayIterator([5, 6]);
|
|
yield from seven_eight();
|
|
yield 9;
|
|
yield 10;
|
|
}
|
|
|
|
function seven_eight() {
|
|
yield 7;
|
|
yield from eight();
|
|
}
|
|
|
|
function eight() {
|
|
yield 8;
|
|
}
|
|
|
|
foreach (count_to_ten() as $num) {
|
|
echo "$num ";
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1 2 3 4 5 6 7 8 9 10
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
|
|
<example>
|
|
<title><command>yield from</command> e valores retornados</title>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function count_to_ten() {
|
|
yield 1;
|
|
yield 2;
|
|
yield from [3, 4];
|
|
yield from new ArrayIterator([5, 6]);
|
|
yield from seven_eight();
|
|
return yield from nine_ten();
|
|
}
|
|
|
|
function seven_eight() {
|
|
yield 7;
|
|
yield from eight();
|
|
}
|
|
|
|
function eight() {
|
|
yield 8;
|
|
}
|
|
|
|
function nine_ten() {
|
|
yield 9;
|
|
return 10;
|
|
}
|
|
|
|
$gen = count_to_ten();
|
|
foreach ($gen as $num) {
|
|
echo "$num ";
|
|
}
|
|
echo $gen->getReturn();
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
&example.outputs;
|
|
<screen>
|
|
<![CDATA[
|
|
1 2 3 4 5 6 7 8 9 10
|
|
]]>
|
|
</screen>
|
|
</example>
|
|
</sect3>
|
|
</sect2>
|
|
</sect1>
|
|
|
|
<sect1 xml:id="language.generators.comparison">
|
|
<title>Comparando geradores com objetos <classname>Iterator</classname></title>
|
|
|
|
<para>
|
|
A principal vantagem dos geradores é a sua simplicidade. Muito menos código
|
|
clichê tem de ser reescrito em comparação com a implementação de uma classe
|
|
<classname>Iterator</classname>, e o código geralmente é muito mais legível.
|
|
Por exemplo, a seguinte função e classe são equivalentes:
|
|
</para>
|
|
|
|
<informalexample>
|
|
<programlisting role="php">
|
|
<![CDATA[
|
|
<?php
|
|
function getLinesFromFile($fileName) {
|
|
if (!$fileHandle = fopen($fileName, 'r')) {
|
|
return;
|
|
}
|
|
|
|
while (false !== $line = fgets($fileHandle)) {
|
|
yield $line;
|
|
}
|
|
|
|
fclose($fileHandle);
|
|
}
|
|
|
|
// ao invés de:
|
|
|
|
class LineIterator implements Iterator {
|
|
protected $fileHandle;
|
|
|
|
protected $line;
|
|
protected $i;
|
|
|
|
public function __construct($fileName) {
|
|
if (!$this->fileHandle = fopen($fileName, 'r')) {
|
|
throw new RuntimeException('Não foi possível abrir o arquivo "' . $fileName . '"');
|
|
}
|
|
}
|
|
|
|
public function rewind() {
|
|
fseek($this->fileHandle, 0);
|
|
$this->line = fgets($this->fileHandle);
|
|
$this->i = 0;
|
|
}
|
|
|
|
public function valid() {
|
|
return false !== $this->line;
|
|
}
|
|
|
|
public function current() {
|
|
return $this->line;
|
|
}
|
|
|
|
public function key() {
|
|
return $this->i;
|
|
}
|
|
|
|
public function next() {
|
|
if (false !== $this->line) {
|
|
$this->line = fgets($this->fileHandle);
|
|
$this->i++;
|
|
}
|
|
}
|
|
|
|
public function __destruct() {
|
|
fclose($this->fileHandle);
|
|
}
|
|
}
|
|
?>
|
|
]]>
|
|
</programlisting>
|
|
</informalexample>
|
|
|
|
<para>
|
|
No entanto, essa flexibilidade tem um custo: os geradores somente movem-se
|
|
para frente e não podem ser rebobinados após a iteração ter sido iniciada.
|
|
Isso também significa que o geradores não pode ser iterado múltiplas vezes:
|
|
o gerador precisará ser reconstruído chamando a função geradora novamente.
|
|
</para>
|
|
|
|
<simplesect role="seealso">
|
|
&reftitle.seealso;
|
|
<para>
|
|
<simplelist>
|
|
<member><link linkend="language.oop5.iterations">Iteração de Objetos</link></member>
|
|
</simplelist>
|
|
</para>
|
|
</simplesect>
|
|
|
|
</sect1>
|
|
</chapter>
|
|
|
|
<!-- Keep this comment at the end of the file
|
|
Local variables:
|
|
mode: sgml
|
|
sgml-omittag:t
|
|
sgml-shorttag:t
|
|
sgml-minimize-attributes:nil
|
|
sgml-always-quote-attributes:t
|
|
sgml-indent-step:1
|
|
sgml-indent-data:t
|
|
indent-tabs-mode:nil
|
|
sgml-parent-document:nil
|
|
sgml-default-dtd-file:"~/.phpdoc/manual.ced"
|
|
sgml-exposed-tags:nil
|
|
sgml-local-catalogs:nil
|
|
sgml-local-ecat-files:nil
|
|
End:
|
|
vim600: syn=xml fen fdm=syntax fdl=2 si
|
|
vim: et tw=78 syn=sgml
|
|
vi: ts=1 sw=1
|
|
-->
|