PHP는 버전 5.5에서 "Generator" 기능을 도입했지만 이 기능은 사람들의 관심을 끌지 못했습니다. PHP 5.4.x에서 PHP 5.5.x로의 정식 마이그레이션에서는 간단한 방법으로 반복자(Iterator)를 구현할 수 있다고 소개되어 있습니다. 하지만 그 외에도 어떤 시나리오에서 생성기를 사용할 수 있나요?
Yield 키워드를 통해 Generator 구현이 완료됩니다. 생성기는 반복기 인터페이스를 구현하는 클래스를 통해 반복기 구현의 복잡성이나 추가 오버헤드가 거의 없이 반복기를 구현하는 간단한 방법을 제공합니다.
문서는 이 간단한 반복자를 보여주는 간단한 예를 제공합니다. 아래 코드를 살펴보십시오.
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; }
더 짧은 함수 정의 외에도 무엇을 얻을 수 있을까요? yield 정확히 어떤 일을 하셨나요? return 문 없이도 첫 번째 함수를 정의할 때 데이터가 반환될 수 있는 이유는 무엇입니까?
반환 값부터 시작해 보겠습니다. 제너레이터는 PHP에서 매우 특별한 기능입니다. 함수에 yield이 포함되어 있으면 이 함수는 더 이상 일반 함수가 아니며 항상 "생성기" 인스턴스를 반환합니다. 생성기는 Iterator 인터페이스를 구현하므로 foreach 순회를 수행할 수 있습니다.
다음으로 Iterator 인터페이스의 메서드를 사용하여 이전 foreach 루프를 다시 작성합니다. 3v4l.org에서 결과를 볼 수 있습니다.
$generator = xrange(1, 100); while($generator->valid()) { print $generator->current() . PHP_EOL; $generator->next(); }
생성기가 더 발전된 기술이라는 것을 확실히 알 수 있습니다. 이제 생성기 내부에서 처리되는 방식을 더 잘 이해하기 위해 새로운 생성기 예제를 작성해 보겠습니다.
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(); }
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
응? Generatorcreated가 먼저 인쇄되는 이유는 무엇입니까? 이는 발전기가 사용될 때까지 아무 작업도 수행하지 않기 때문입니다. 위의 예에서 생성기 실행을 시작하는 코드는 $generator->valid()**입니다. 생성기가 첫 번째 **yield**까지 실행되고 제어 흐름을 호출자 **$generator->valid()에게 반환하는 것을 볼 수 있습니다. $generator->next() 호출되면 생성기 실행이 재개되고 다음 yield 실행이 다시 중지되는 식으로 더 이상 생성기가 없을 때까지 계속됩니다. # 🎜🎜 #yield 지금까지. 이제 yield에서 일시 중지 및 재개를 수행할 수 있는 터미널 기능이 있습니다. 이 기능을 사용하면 클라이언트에 필요한 연기 기능을 작성할 수 있습니다.
GitHub API에서 모든 사용자를 읽는 함수를 만들 수 있습니다. 페이지 매김이 지원되지만 필요한 경우에만 이러한 세부 정보를 숨기고 데이터의 다음 페이지를 가져올 수 있습니다.yield을 사용하면 현재 페이지에서 각 사용자 데이터를 얻을 수 있습니다. 현재 페이지의 모든 사용자를 얻을 때까지 다음 페이지의 데이터를 얻을 수 있습니다.
class GitHubClient { function getUsers(): Iterator { $uri = '/users'; do { $response = $this->get($uri); foreach ($response->items as $user) { yield $user; } $uri = $response->nextUri; } while($uri !== null); } }클라이언트는 언제든지 모든 사용자를 반복하거나 반복을 중지할 수 있습니다. 생성자를 반복자로 사용하는 것은 정말 지루합니다예, 당신 말이 맞습니다. 위에서 제공한 모든 설명은 PHP 문서를 통해 누구든지 얻을 수 있습니다. 그러나 반복자로서 이러한 사용은 강력한 기능의 절반도 사용하지 않습니다. 생성기는
Iterator 인터페이스의 일부가 아닌 send() 및 throw() 함수도 제공합니다. 앞서 생성기 실행을 일시 중지하고 재개하는 기능에 대해 이야기했습니다. 생성기를 복원해야 하는 경우 Generator::next() 메서드를 사용할 수 있을 뿐만 아니라 Generator::send() 및 #을 사용할 수도 있습니다. 🎜🎜#Generator ::throw() 메서드.
Generator::send()을 사용하면 yield의 반환 값을 지정할 수 있으며 Generator::throw( )# 🎜🎜# yield에 예외가 발생하도록 허용합니다. 이러한 방법을 통해 생성기에서 데이터를 얻을 수 있을 뿐만 아니라 새 데이터를 생성기로 보낼 수도 있습니다. 코루틴을 사용한 협력적 멀티태스킹에서 가져온 Logger
로그 예제를 살펴보겠습니다(이 문서를 읽는 것이 좋습니다). 여기서는function logger($filename) { $fileHandle = fopen($filename, 'a'); while (true) { fwrite($fileHandle, yield . "\n"); } } $logger = logger(__DIR__ . '/log'); $logger->send('Foo'); $logger->send('Bar');yield이 표현으로 사용되었습니다. 데이터를 보내면 데이터는
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请求这类操作。
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; } });
但是,拥有异步功能的协程并非只能够在 yield 右侧出现变量,还可以在它的左侧。这就是我们前面提到的解析器。
$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"); }
解析器会缓存所有输入直到接收的是 rn。这类生成器解析器并不能简化简单协议处理(如换行分隔符协议),但是对于复杂的解析器,如在服务器解析 HTTP 请求的 Aerys。
相关推荐:
위 내용은 PHP 생성기 사용법에 대해 이야기합시다의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!