(PHP 5 >= 5.5.0, PHP 7)
生成器提供了一種更容易的方法來實現簡單的對象迭代,相比較定義類實現 Iterator 接口的方式,性能開銷和復雜性大大降低。
生成器允許你在 foreach 代碼塊中寫代碼來迭代一組數據而不需要在內存中創建一個數組, 那會使你的內存達到上限,或者會占據可觀的處理時間。相反,你可以寫一個生成器函數,就像一個普通的自定義函數一樣, 和普通函數只返回一次不同的是, 生成器可以根據需要 yield 多次,以便生成需要迭代的值。
一個簡單的例子就是使用生成器來重新實現 range() 函數。 標准的 range() 函數需要在內存中生成一個數組包含每一個在它范圍內的值,然後返回該數組, 結果就是會產生多個很大的數組。 比如,調用 range(0, 1000000) 將導致內存占用超過 100 MB。
做為一種替代方法, 我們可以實現一個 xrange() 生成器, 只需要足夠的內存來創建 Iterator 對象並在內部跟蹤生成器的當前狀態,這樣只需要不到1K字節的內存。
Example #1 將 range() 實現為生成器
<?php function xrange($start, $limit, $step = 1) { if ($start < $limit) { if ($step <= 0) { throw new LogicException('Step must be +ve'); } for ($i = $start; $i <= $limit; $i += $step) { yield $i; } } else { if ($step >= 0) { throw new LogicException('Step must be -ve'); } for ($i = $start; $i >= $limit; $i += $step) { yield $i; } } } /* * 注意下面range()和xrange()輸出的結果是一樣的。 */ echo 'Single digit odd numbers from range(): '; foreach (range(1, 9, 2) as $number) { echo "$number "; } echo "\n"; echo 'Single digit odd numbers from xrange(): '; foreach (xrange(1, 9, 2) as $number) { echo "$number "; } ?>
以上例程會輸出:
Single digit odd numbers from range(): 1 3 5 7 9 Single digit odd numbers from xrange(): 1 3 5 7 9
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.
一個生成器函數看起來像一個普通的函數,不同的是普通函數返回一個值,而一個生成器可以 yield 生成許多它所需要的值。
當一個生成器被調用的時候,它返回一個可以被遍歷的對象.當你遍歷這個對象的時候(例如通過一個foreach循環),PHP 將會在每次需要值的時候調用生成器函數,並在產生一個值之後保存生成器的狀態,這樣它就可以在需要產生下一個值的時候恢復調用狀態。
一旦不再需要產生更多的值,生成器函數可以簡單退出,而調用生成器的代碼還可以繼續執行,就像一個數組已經被遍歷完了。
Note:
一個生成器不可以返回值: 這樣做會產生一個編譯錯誤。然而return空是一個有效的語法並且它將會終止生成器繼續執行。
yield關鍵字
生成器函數的核心是yield關鍵字。它最簡單的調用形式看起來像一個return申明,不同之處在於普通return會返回值並終止函數的執行,而yield會返回一個值給循環調用此生成器的代碼並且只是暫停執行生成器函數。
Example #1 一個簡單的生成值的例子
<?php function gen_one_to_three() { for ($i = 1; $i <= 3; $i++) { //注意變量$i的值在不同的yield之間是保持傳遞的。 yield $i; } } $generator = gen_one_to_three(); foreach ($generator as $value) { echo "$value\n"; } ?>以上例程會輸出:
1 2 3Note:
在內部會為生成的值配對連續的整型索引,就像一個非關聯的數組。
如果在一個表達式上下文(例如在一個賦值表達式的右側)中使用yield,你必須使用圓括號把yield申明包圍起來。 例如這樣是有效的:
$data = (yield $value);而這樣就不合法,並且在PHP5中會產生一個編譯錯誤:
$data = yield $value;The parenthetical restrictions do not apply in PHP 7.
這個語法可以和生成器對象的Generator::send()方法配合使用。
指定鍵名來生成值
PHP的數組支持關聯鍵值對數組,生成器也一樣支持。所以除了生成簡單的值,你也可以在生成值的時候指定鍵名。
如下所示,生成一個鍵值對與定義一個關聯數組十分相似。
Example #2 生成一個鍵值對
<?php /* * 下面每一行是用分號分割的字段組合,第一個字段將被用作鍵名。 */ $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"; } ?>以上例程會輸出:
1: PHP Likes dollar signs 2: Python Likes whitespace 3: Ruby Likes blocks和之前生成簡單值類型一樣,在一個表達式上下文中生成鍵值對也需要使用圓括號進行包圍:
$data = (yield $key => $value);生成null值
Yield可以在沒有參數傳入的情況下被調用來生成一個
NULL
值並配對一個自動的鍵名。Example #3 生成
NULL
s<?php function gen_three_nulls() { foreach (range(1, 3) as $i) { yield; } } var_dump(iterator_to_array(gen_three_nulls())); ?>以上例程會輸出:
array(3) { [0]=> NULL [1]=> NULL [2]=> NULL }使用引用來生成值
生成函數可以像使用值一樣來使用引用生成。這個和returning references from functions(從函數返回一個引用)一樣:通過在函數名前面加一個引用符號。
Example #4 使用引用來生成值
<?php function &gen_reference() { $value = 3; while ($value > 0) { yield $value; } } /* * 我們可以在循環中修改$number的值,而生成器是使用的引用值來生成,所以gen_reference()內部的$value值也會跟著變化。 */ foreach (gen_reference() as &$number) { echo (--$number).'... '; } ?>以上例程會輸出:
2... 1... 0...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
<?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 "; } ?>以上例程會輸出:
1 2 3 4 5 6 7 8 9 10Example #6 yield from and return values
<?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(); ?>以上例程會輸出:
1 2 3 4 5 6 7 8 9 10Comparing 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:
<?php function getLinesFromFile($fileName) { if (!$fileHandle = fopen($fileName, 'r')) { return; } while (false !== $line = fgets($fileHandle)) { yield $line; } fclose($fileHandle); } // versus... class LineIterator implements Iterator { protected $fileHandle; protected $line; protected $i; public function __construct($fileName) { if (!$this->fileHandle = fopen($fileName, 'r')) { throw new RuntimeException('Couldn\'t open file "' . $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); } } ?>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