Home > Article > Backend Development > PHP learning--generatorsGenerators,--generatorsgenerators_PHP tutorial
(PHP 5 >= 5.5.0, PHP 7)
Generators provide an easier way to implement simple object iteration. Compared with defining a class to implement the Iterator interface, the performance overhead and complexity are greatly reduced.
Generators allow you to write code in a foreach block to iterate over a set of data without creating an array in memory, which would hit your memory limit or take up considerable processing time. Instead, you can write a generator function, just like a normal custom function, and instead of a normal function returning only once, the generator can yield as many times as needed in order to generate values that need to be iterated over.
A simple example is to use a generator to reimplement the range() function. The standard range() function needs to generate an array in memory containing every value within its range, and then return the array, resulting in multiple large arrays. For example, calling range(0, 1000000) will cause the memory usage to exceed 100 MB.
As an alternative, we can implement a xrange() generator that only requires enough memory to create an Iterator object and internally track the current state of the generator, so that only Requires less than 1K bytes of memory.
Example #1 Implement range() as a generator
<?<span>php </span><span>function</span> xrange(<span>$start</span>, <span>$limit</span>, <span>$step</span> = 1<span>) { </span><span>if</span> (<span>$start</span> < <span>$limit</span><span>) { </span><span>if</span> (<span>$step</span> <= 0<span>) { </span><span>throw</span> <span>new</span> LogicException('Step must be +ve'<span>); } </span><span>for</span> (<span>$i</span> = <span>$start</span>; <span>$i</span> <= <span>$limit</span>; <span>$i</span> += <span>$step</span><span>) { yield </span><span>$i</span><span>; } } </span><span>else</span><span> { </span><span>if</span> (<span>$step</span> >= 0<span>) { </span><span>throw</span> <span>new</span> LogicException('Step must be -ve'<span>); } </span><span>for</span> (<span>$i</span> = <span>$start</span>; <span>$i</span> >= <span>$limit</span>; <span>$i</span> += <span>$step</span><span>) { yield </span><span>$i</span><span>; } } } </span><span>/*</span><span> * 注意下面range()和xrange()输出的结果是一样的。 </span><span>*/</span> <span>echo</span> 'Single digit odd numbers from range(): '<span>; </span><span>foreach</span> (<span>range</span>(1, 9, 2) <span>as</span> <span>$number</span><span>) { </span><span>echo</span> "<span>$number</span> "<span>; } </span><span>echo</span> "\n"<span>; </span><span>echo</span> 'Single digit odd numbers from xrange(): '<span>; </span><span>foreach</span> (xrange(1, 9, 2) <span>as</span> <span>$number</span><span>) { </span><span>echo</span> "<span>$number</span> "<span>; } </span>?>
The above routine will output:
Single digit odd numbers from range(): <span>1</span> <span>3</span> <span>5</span> <span>7</span> <span>9</span><span> Single digit odd numbers from xrange(): </span><span>1</span> <span>3</span> <span>5</span> <span>7</span> <span>9</span>
When a generator function is called for the first time, an object of the internal Generator class is returned. This object implements the Iterator interface in much the same way as a forward-only iterator object would, and provides methods that can be called to manipulate the state of the generator, including sending values to and returning values from it.
A generator function looks like an ordinary function, except that an ordinary function returns a value, while a generator can yield as many values as it needs.
When a generator is called, it returns an object that can be iterated over. When you iterate over the object (such as through a foreach loop), PHP will call the generator function each time a value is needed. , and save the generator's state after producing a value, so that it can restore the calling state when it needs to produce the next value.
Once no more values need to be produced, the generator function can simply exit, and the code that called the generator can continue executing as if an array had been iterated over.
Note:
A generator cannot return a value: doing so will generate a compilation error. However, return null is valid syntax and will terminate the generator and continue execution.
yield keyword
The core of thegenerator function is the yield keyword. In its simplest form, it looks like a return statement. The difference is that a normal return returns a value and terminates the execution of the function, while yield returns a value to the code that loops through the generator and simply pauses the execution of the generator function.
Example #1 A simple example of generating values
<?<span>php </span><span>function</span><span> gen_one_to_three() { </span><span>for</span> (<span>$i</span> = 1; <span>$i</span> <= 3; <span>$i</span>++<span>) { </span><span>//</span><span>注意变量$i的值在不同的yield之间是保持传递的。</span> yield <span>$i</span><span>; } } </span><span>$generator</span> =<span> gen_one_to_three(); </span><span>foreach</span> (<span>$generator</span> <span>as</span> <span>$value</span><span>) { </span><span>echo</span> "<span>$value</span>\n"<span>; } </span>?>The above routine will output:
<span>1</span> <span>2</span> <span>3</span>Note:
Internally the generated values are paired with consecutive integer indices, just like a non-associative array.
If you use yield in an expression context (such as on the right side of an assignment expression), you must use parentheses to surround the yield declaration. For example this is valid:
<span>$data</span> = (yield <span>$value</span>);This is illegal and will generate a compilation error in PHP5:
<span>$data</span> = yield <span>$value</span>;The parenthetical restrictions do not apply in PHP 7.
This syntax can be used in conjunction with the Generator::send() method of the generator object.
Specify the key name to generate the value
PHP’s array supports associated key-value pair arrays, and generators also support it. So in addition to generating simple values, you can also specify key names when generating values.
As shown below, generating a key-value pair is very similar to defining an associative array.
Example #2 Generate a key-value pair
<?<span>php </span><span>/*</span><span> * 下面每一行是用分号分割的字段组合,第一个字段将被用作键名。 </span><span>*/</span> <span>$input</span> = <<<'EOF' 1<span>;PHP;Likes dollar signs </span>2<span>;Python;Likes whitespace </span>3<span>;Ruby;Likes blocks EOF; </span><span>function</span> input_parser(<span>$input</span><span>) { </span><span>foreach</span> (<span>explode</span>("\n", <span>$input</span>) <span>as</span> <span>$line</span><span>) { </span><span>$fields</span> = <span>explode</span>(';', <span>$line</span><span>); </span><span>$id</span> = <span>array_shift</span>(<span>$fields</span><span>); yield </span><span>$id</span> => <span>$fields</span><span>; } } </span><span>foreach</span> (input_parser(<span>$input</span>) <span>as</span> <span>$id</span> => <span>$fields</span><span>) { </span><span>echo</span> "<span>$id</span>:\n"<span>; </span><span>echo</span> " <span>$fields</span>[0]\n"<span>; </span><span>echo</span> " <span>$fields</span>[1]\n"<span>; } </span>?>以上例程会输出:
<span>1</span><span>: PHP Likes dollar signs </span><span>2</span><span>: Python Likes whitespace </span><span>3</span><span>: Ruby Likes blocks</span>和之前生成简单值类型一样,在一个表达式上下文中生成键值对也需要使用圆括号进行包围:
<span>$data</span> = (yield <span>$key</span> => <span>$value</span>);生成null值
Yield可以在没有参数传入的情况下被调用来生成一个
NULL
值并配对一个自动的键名。Example #3 生成
NULL
s<?<span>php </span><span>function</span><span> gen_three_nulls() { </span><span>foreach</span> (<span>range</span>(1, 3) <span>as</span> <span>$i</span><span>) { yield; } } </span><span>var_dump</span><span>(iterator_to_array(gen_three_nulls())); </span>?>以上例程会输出:
array(<span>3</span><span>) { [</span><span>0</span>]=><span> NULL [</span><span>1</span>]=><span> NULL [</span><span>2</span>]=><span> NULL }</span>使用引用来生成值
生成函数可以像使用值一样来使用引用生成。这个和returning references from functions(从函数返回一个引用)一样:通过在函数名前面加一个引用符号。
Example #4 使用引用来生成值
<?<span>php </span><span>function</span> &<span>gen_reference() { </span><span>$value</span> = 3<span>; </span><span>while</span> (<span>$value</span> > 0<span>) { yield </span><span>$value</span><span>; } } </span><span>/*</span><span> * 我们可以在循环中修改$number的值,而生成器是使用的引用值来生成,所以gen_reference()内部的$value值也会跟着变化。 </span><span>*/</span> <span>foreach</span> (gen_reference() <span>as</span> &<span>$number</span><span>) { </span><span>echo</span> (--<span>$number</span>).'... '<span>; } </span>?>以上例程会输出:
<span>2</span>... <span>1</span>... <span>0</span>...Generator delegation via yield from ¶
In PHP 7, generator delegation allows you to yield values from another generator, Traversable object, or array by using the yield from keyword. The outer generator will then yield all values from the inner generator, object, or array until that is no longer valid, after which execution will continue in the outer generator.
If a generator is used with yield from, the yield from expression will also return any value returned by the inner generator.
Example #5 Basic use of yield from
<?<span>php </span><span>function</span><span> count_to_ten() { yield </span>1<span>; yield </span>2<span>; yield from [</span>3, 4<span>]; yield from </span><span>new</span> ArrayIterator([5, 6<span>]); yield from seven_eight(); yield </span>9<span>; yield </span>10<span>; } </span><span>function</span><span> seven_eight() { yield </span>7<span>; yield from eight(); } </span><span>function</span><span> eight() { yield </span>8<span>; } </span><span>foreach</span> (count_to_ten() <span>as</span> <span>$num</span><span>) { </span><span>echo</span> "<span>$num</span> "<span>; } </span>?>以上例程会输出:
<span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> <span>6</span> <span>7</span> <span>8</span> <span>9</span> <span>10</span>Example #6 yield from and return values
<?<span>php </span><span>function</span><span> count_to_ten() { yield </span>1<span>; yield </span>2<span>; yield from [</span>3, 4<span>]; yield from </span><span>new</span> ArrayIterator([5, 6<span>]); yield from seven_eight(); </span><span>return</span><span> yield from nine_ten(); } </span><span>function</span><span> seven_eight() { yield </span>7<span>; yield from eight(); } </span><span>function</span><span> eight() { yield </span>8<span>; } </span><span>function</span><span> nine_ten() { yield </span>9<span>; </span><span>return</span> 10<span>; } </span><span>$gen</span> =<span> count_to_ten(); </span><span>foreach</span> (<span>$gen</span> <span>as</span> <span>$num</span><span>) { </span><span>echo</span> "<span>$num</span> "<span>; } </span><span>echo</span> <span>$gen</span>-><span>getReturn(); </span>?>以上例程会输出:
<span>1</span> <span>2</span> <span>3</span> <span>4</span> <span>5</span> <span>6</span> <span>7</span> <span>8</span> <span>9</span> <span>10</span>Comparing generators with Iterator objects
The primary advantage of generators is their simplicity. Much less boilerplate code has to be written compared to implementing an Iterator class, and the code is generally much more readable. For example, the following function and class are equivalent:
<?<span>php </span><span>function</span> getLinesFromFile(<span>$fileName</span><span>) { </span><span>if</span> (!<span>$fileHandle</span> = <span>fopen</span>(<span>$fileName</span>, 'r'<span>)) { </span><span>return</span><span>; } </span><span>while</span> (<span>false</span> !== <span>$line</span> = <span>fgets</span>(<span>$fileHandle</span><span>)) { yield </span><span>$line</span><span>; } </span><span>fclose</span>(<span>$fileHandle</span><span>); } </span><span>//</span><span> versus...</span> <span>class</span> LineIterator <span>implements</span><span> Iterator { </span><span>protected</span> <span>$fileHandle</span><span>; </span><span>protected</span> <span>$line</span><span>; </span><span>protected</span> <span>$i</span><span>; </span><span>public</span> <span>function</span> __construct(<span>$fileName</span><span>) { </span><span>if</span> (!<span>$this</span>->fileHandle = <span>fopen</span>(<span>$fileName</span>, 'r'<span>)) { </span><span>throw</span> <span>new</span> RuntimeException('Couldn\'t open file "' . <span>$fileName</span> . '"'<span>); } } </span><span>public</span> <span>function</span> <span>rewind</span><span>() { </span><span>fseek</span>(<span>$this</span>->fileHandle, 0<span>); </span><span>$this</span>->line = <span>fgets</span>(<span>$this</span>-><span>fileHandle); </span><span>$this</span>->i = 0<span>; } </span><span>public</span> <span>function</span><span> valid() { </span><span>return</span> <span>false</span> !== <span>$this</span>-><span>line; } </span><span>public</span> <span>function</span> <span>current</span><span>() { </span><span>return</span> <span>$this</span>-><span>line; } </span><span>public</span> <span>function</span> <span>key</span><span>() { </span><span>return</span> <span>$this</span>-><span>i; } </span><span>public</span> <span>function</span> <span>next</span><span>() { </span><span>if</span> (<span>false</span> !== <span>$this</span>-><span>line) { </span><span>$this</span>->line = <span>fgets</span>(<span>$this</span>-><span>fileHandle); </span><span>$this</span>->i++<span>; } } </span><span>public</span> <span>function</span><span> __destruct() { </span><span>fclose</span>(<span>$this</span>-><span>fileHandle); } } </span>?>This flexibility does come at a cost, however: generators are forward-only iterators, and cannot be rewound once iteration has started. This also means that the same generator can't be iterated over multiple times: the generator will need to either be rebuilt by calling the generator function again, or cloned via the clone keyword.
摘自:http://php.net/manual/zh/language.generators.php