PHP はバージョン 5.5 で「ジェネレーター」機能を導入しましたが、この機能は人々の注目を集めませんでした。 PHP 5.4.x から PHP 5.5.x への正式な移行では、反復子 (Iterator) を簡単な方法で実装できることが紹介されています。しかしそれを超えて、ジェネレーターはどのようなシナリオで使用できるのでしょうか?
ジェネレーターの実装は、yield キーワードによって完了します。ジェネレーターは、追加のオーバーヘッドや、イテレーター インターフェイスを実装するクラスを介したイテレーターの実装の複雑さをほとんど伴わずに、イテレーターを実装する簡単な方法を提供します。
ドキュメントには、この単純な反復子を示す簡単な例が記載されています。次のコードを見てください:
function xrange($start, $limit, $step = 1) { for ($i = $start; $i <= $limit; $i += $step) { yield $i; } }
これを反復子サポートのない配列と比較してみましょう:
foreach xrange($start, $limit, $step = 1) { $elements = []; for ($i = $start; $i <= $limit; $i += $step) { $elements[] = $i; } return $elements; }
両方のバージョン関数サポートの foreach すべての要素を反復処理する:
foreach (xrange(1, 100) as $i) { print $i . PHP_EOL; }
では、短い関数定義のほかに、他に何が得られるでしょうか? 収量 具体的には何をしましたか? return ステートメントがなくても、最初の関数が定義されているときにデータが返されるのはなぜですか?
戻り値から始めましょう。ジェネレーターは、PHP の非常に特殊な関数です。関数に yield が含まれる場合、その関数は通常の関数ではなくなり、常に「Generator」インスタンスを返します。ジェネレーターは Iterator インターフェイスを実装しているため、foreach トラバーサルが可能です。
次に、Iterator インターフェイスのメソッドを使用して、前の foreach ループを書き直します。結果は 3v4l.org でご覧いただけます。
$generator = xrange(1, 100); while($generator->valid()) { print $generator->current() . PHP_EOL; $generator->next(); }
ジェネレーターがより高度なテクノロジであることが明確にわかります。次に、ジェネレーター内でどのように処理されるかをよりよく理解するために、新しいジェネレーターの例を作成してみましょう。
えっ?Generator created が最初に出力されるのはなぜですか?これは、ジェネレーターは使用されるまで何も行わないためです。上記の例では、ジェネレーターの実行を開始するコード $generator->valid()** です。ジェネレーターが最初の **yield** まで実行され、呼び出し元 **$generator->valid() に制御フローを返すことがわかります。 $generator->next() 呼び出されると、ジェネレーターの実行が再開され、次の yield で再び実行が停止され、yield# がなくなるまで同様に実行されます。 ## それまで。任意の yield で一時停止と再開を実行できる端末関数が追加されました。この機能を使用すると、クライアントが必要とする遅延関数を作成できます。 GitHub API からすべてのユーザーを読み取る関数を作成できます。ページネーションはサポートされていますが、これらの詳細を非表示にして、必要な場合にのみデータの次のページを取得することができます。
yield を使用して、現在のページから各ユーザーのデータを取得できます。現在のページのすべてのユーザーが取得されるまで、次のページのデータを取得できます。 function foobar() {
print 'foobar - start' . PHP_EOL;
for ($i = 0; $i < 5; $i++) {
print 'foobar - yielding...' . PHP_EOL;
yield $i;
print 'foobar - continued...' . PHP_EOL;
}
print 'foobar - end' . PHP_EOL;
}
$generator = foobar();
print 'Generator created' . PHP_EOL;
while ($generator->valid()) {
print "Getting current value from the generator..." . PHP_EOL;
print $generator->current() . PHP_EOL;
$generator->next();
}
クライアントは、すべてのユーザーを反復処理したり、いつでもトラバーサルを停止したりできます。
ジェネレータをイテレータとして使用するのは本当に退屈です
インターフェイスの一部ではない send() および throw() 関数も提供します。ジェネレーターの実行を一時停止および再開する機能については以前に説明しました。ジェネレーターを復元する必要がある場合は、Generator::next() メソッドを使用できるだけでなく、Generator::send() および Generator:: も使用できます。 throw() メソッド。
Generator::send()では yield の戻り値を指定でき、Generator::throw() では次のことができます。 yield は例外をスローします。これらのメソッドを通じて、ジェネレーターからデータを取得できるだけでなく、新しいデータをジェネレーターに送信することもできます。 コルーチンを使用した協調マルチタスクから取得した
Logger ログの例を見てみましょう (この記事を読むことを強くお勧めします)。ここでは Generator created
foobar - start
foobar - yielding...
Getting current value from the generator...
1
foobar - continued
foobar - yielding...
Getting current value from the generator...
2
foobar - continued
foobar - yielding...
Getting current value from the generator...
3
foobar - continued
foobar - yielding...
Getting current value from the generator...
4
foobar - continued
foobar - yielding...
Getting current value from the generator...
5
foobar - continued
foobar - end
が式として使用されます。データを送信すると、データは yield から返され、パラメータとして fwrite() に渡されます。 正直に言うと、この例は実際のプロジェクトでは役に立ちません。これは
Generator::send() の使用原則を示すためにのみ使用されますが、データを送信できるだけでは大きな効果はありません。通常の関数をサポートするクラスがあれば別ですが。 使用生成器的乐趣来自于通过 yield 创建数据,然后由「生成器执行程序(generator runner)」依据这个数据来处理业务,然后再继续执行生成器。这就是「协程(coroutines)」和「状态流解析器(stateful streaming parsers)」实例。在讲解协程和状态流解析器之前,我们快速浏览一下如何在生成器中返回数据,我们还没有将接触这方面的知识。从 PHP 5.5 开始我们可以在生成器内部使用 return; 语句,但是不能返回任何值。执行 return; 语句的唯一目的是结束生成器执行。 不过从 PHP 7.0 起支持返回值。这个功能在用于迭代时可能有些奇怪,但是在其他使用场景如协程时将非常有用,例如,当我们在执行一个生成器时我们可以依据返回值处理,而无需直接对生成器进行操作。下一节我们将讲解 return 语句在协程中的使用。 Amp 是一款 PHP 异步编程的框架。支持异步协程功能,本质上是等待处理结果的占位符。「生成器执行程序」为 Coroutine类。它会订阅异步生成器(yielded promise),当有执行结果可用时则继续生成器处理。如果处理失败,则会抛出异常给生成器。你可以到 amphp/amp 版本库查看实现细节。在 Amp 中的 Coroutine 本身就是一个 Promise。如果这个协程抛出未经捕获的异常,这个协程就执行失败了。如果解析成功,那么就返回一个值。这个值看起来和普通函数的返回值并无二致,只不过它处于异步执行环境中。这就是需要生成器需要有返回值的意义,这也是为何我们将这个特性加入到 PHP 7.0 中的原因,我们会将最后执行的yield 值作为返回值,但这不是一个好的解决方案。 Amp 可以像编写阻塞代码一样编写非阻塞代码,同时允许在同一进程中执行其它非阻塞事件。一个使用场景是,同时对一个或多个第三方 API 并行的创建多个 HTTP 请求,但不限于此。得益于事件循环,可以同时处理多个 I/O 处理,而不仅仅是只能处理多个 HTTP请求这类操作。 但是,拥有异步功能的协程并非只能够在 yield 右侧出现变量,还可以在它的左侧。这就是我们前面提到的解析器。 解析器会缓存所有输入直到接收的是 rn。这类生成器解析器并不能简化简单协议处理(如换行分隔符协议),但是对于复杂的解析器,如在服务器解析 HTTP 请求的 Aerys。 相关推荐:异步生成器
Loop::run(function() {
$uris = [
"https://google.com/",
"https://github.com/",
"https://stackoverflow.com/",
];
$client = new Amp\Artax\DefaultClient;
$promises = [];
foreach ($uris as $uri) {
$promises[$uri] = $client->request($uri);
}
$responses = yield $promises;
foreach ($responses as $uri => $response) {
print $uri . " - " . $response->getStatus() . PHP_EOL;
}
});
$parse = new Parser((function(){
while (true) {
$line = yield "\r\n";
if (trim($line) === "") {
continue;
}
print "New item: {$line}" . PHP_EOL;
}
})());
for ($i = 0; $i < 100; $i++) {
$parser->push("bar\r");
$parser->push("\nfoo");
}
以上がPHPジェネレーターの使い方について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。