PHP Conference Japan 2024

Generator::send

(PHP 5 >= 5.5.0, PHP 7, PHP 8)

Generator::send傳送值給產生器

說明

public Generator::send(混合 $value): 混合

將指定的值作為目前 yield 表達式的結果傳送給產生器,並繼續執行產生器。

如果呼叫此方法時產生器不在 yield 表達式,它會先前進到第一個 yield 表達式,然後再傳送值。因此,不需要像 Python 那樣使用 Generator::next() 呼叫來「啟動」PHP 產生器。

參數

value

要傳送到產生器的值。此值將會是產生器目前所在 yield 表達式的返回值。

返回值

返回 yield 的值。

範例

範例 #1 使用 Generator::send() 注入值

<?php
function printer() {
echo
"I'm printer!".PHP_EOL;
while (
true) {
$string = yield;
echo
$string.PHP_EOL;
}
}

$printer = printer();
$printer->send('Hello world!');
$printer->send('Bye world!');
?>

以上範例將輸出

I'm printer!
Hello world!
Bye world!

新增註解

使用者貢獻的註解 5 則註解

sfroelich01 at sp dot gm dot ail dot am dot com
11 年前
閱讀範例後,有點難理解究竟該如何使用它。以下範例是一個簡單的應用示範。

<?php
function nums() {
for (
$i = 0; $i < 5; ++$i) {
//從呼叫者取得一個值
$cmd = (yield $i);

if(
$cmd == 'stop')
return;
//離開函式
}
}

$gen = nums();
foreach(
$gen as $v)
{
if(
$v == 3)//我們滿足條件了
$gen->send('stop');

echo
"{$v}\n";
}

//輸出
0
1
2
3
?>
php at didatus dot de
3 年前
如果您想在 foreach 迴圈中使用 generator::send(),您很可能會得到意想不到的結果。Generator::send() 方法會恢復產生器,這意味著產生器中的指標會移動到產生器列表中的下一個元素。

以下是一個例子

<?php

class ApiDummy
{
private static
$apiDummyData = ['a', 'b', 'c', 'd', 'e'];

public static function
getAll(): Generator {
foreach (
self::$apiDummyData as $entry) {
echo
'產生 $elem' . PHP_EOL;
$newElem = (yield $entry);
echo
'yield 返回:' . $newElem . PHP_EOL;
}
}
}

$generator = ApiDummy::getAll();

// 範例迭代一,結果不如預期
foreach ($generator as $elem) {
echo
'從產生器取得的值:' . $elem . PHP_EOL;
$generator->send($elem . '+');
}

// 範例迭代二,預期結果
while ($generator->valid()) {
$elem = $generator->current();
echo
'從產生器取得的值:' . $elem . PHP_EOL;
$generator->send($elem . '+');
}
?>

範例迭代一的結果
產生 $elem
從產生器取得的值:a
yield 返回:a+
產生 $elem
產生 $elem
產生 $elem
從產生器取得的值:c
yield 返回:c+
產生 $elem
產生 $elem
產生 $elem
從產生器取得的值:e
yield 返回:e+

如您所見,值 b 和 d 沒有被印出,也沒有被加上 + 符號。
foreach 迴圈會收到第一個 yield,而 send 呼叫會在第一個迴圈內造成第二個 yield。因此,第二個迴圈已經收到第三個 yield,依此類推。

為了避免這種情況,一個解決方案是使用 while 迴圈以及 Generator::send() 方法來移動產生器游標,並使用 Generator::current() 方法來取得目前的值。迴圈可以用 Generator::valid() 方法來控制,如果產生器已完成,則會返回 false。請參閱範例迭代二。

範例迭代二的預期結果
產生 $elem
從產生器取得的值:a
yield 返回:a+
產生 $elem
從產生器取得的值:a
yield 返回:a+
產生 $elem
從產生器取得的值:c
yield 返回:c+
產生 $elem
產生器產生的值:d
yield 返回值:d+
產生 $elem
從產生器取得的值:e
yield 返回:e+
example.com 的匿名用戶
5 年前
從 7.3 版本開始,在 foreach 迴圈中產生器的行為取決於它是否預期接收資料。如果您遇到「跳過」的情況,這點很重要。

<?php
class X implements IteratorAggregate {
public function
getIterator(){
yield from [
1,2,3,4,5];
}
public function
getGenerator(){
foreach (
$this as $j => $each){
echo
"getGenerator(): 產生的值: {$j} => {$each}\n";
$val = (yield $j => $each);
yield;
// 忽略 foreach 的 next()
echo "getGenerator(): 收到的值: {$j} => {$val}\n";
}
}
}
$x = new X;

foreach (
$x as $i => $val){
echo
"getIterator(): {$i} => {$val}\n";
}
echo
"\n";

$gen = $x->getGenerator();
foreach (
$gen as $j => $val){
echo
"getGenerator(): 傳送的值: {$j} => {$val}\n";
$gen->send($val);
}
?>

getIterator(): 0 => 1
getIterator(): 1 => 2
getIterator(): 2 => 3
getIterator(): 3 => 4
getIterator(): 4 => 5

getGenerator(): 產生的值: 0 => 1
getGenerator(): 傳送的值: 0 => 1
getGenerator(): 收到的值: 0 => 1
getGenerator(): 產生的值: 1 => 2
getGenerator(): 傳送的值: 1 => 2
getGenerator(): 收到的值: 1 => 2
getGenerator(): 產生的值: 2 => 3
getGenerator(): 傳送的值: 2 => 3
getGenerator(): 收到的值: 2 => 3
getGenerator(): 產生的值: 3 => 4
getGenerator(): 傳送的值: 3 => 4
getGenerator(): 收到的值: 3 => 4
getGenerator(): 產生的值: 4 => 5
getGenerator(): 傳送的值: 4 => 5
getGenerator(): 收到的值: 4 => 5
sergei dot solomonov at gmail dot com
11 年前
<?php
function foo() {
$string = yield;
echo
$string;
for (
$i = 1; $i <= 3; $i++) {
yield
$i;
}
}

$generator = foo();
$generator->send('Hello world!');
foreach (
$generator as $value) echo "$value\n";
?>

這段程式碼會產生以下錯誤
PHP 致命錯誤:未捕捉到的例外 'Exception',訊息為 '無法倒回已經執行的生成器'。
foreach 內部會呼叫 rewind,你應該記住這一點!
baohx2000 at gmail dot com
5 年前
我發現反向生成器(使用 $x = yield)是處理分塊批次處理的好方法。在迭代數據時,一旦將特定數量饋送到生成器,它就會處理並重置數據。例如,您可以每 500 筆記錄執行一次批量 MySQL 插入。

範例(請注意 null 的處理,您會將 null 傳送到生成器以處理前一批次之後的剩餘資料)

function importer()
{
$max = 500;
$items = [];
while (true) {
$item = yield;
if ($item !== null) {
$items[] = yield;
}
if ($item === null || count($items) >= $max) {
// 執行批次操作
$items = [];
}
}
}
To Top