文檔提供了一個簡單的實例演示這個簡單的迭代器,請看下面的代碼:
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,那么這個函數即不再是一個普通函數,它永遠返回一個「Generator(生成器)」實例。生成器實現了 Iterator 接口,這就是為何它能夠進行 foreach 遍歷的原因。
接下來我使用 Iterator 接口中的方法,對之前的 foreach 循環進行重寫。你可以在 3v4l.org 查看結果。
$generator = xrange(1, 100);while($generator- valid()) { print $generator- html' target='_blank'>current() . PHP_EOL; $generator- next();}
我們可以清楚的看到生成器是更高級的技術,現在讓我們編寫一個新的生成器示例來更好的理解到底在生成器內部是如何進行處理的吧。
function foobar() { print foobar - start . PHP_EOL; for ($i = 0; $i $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 createdfoobar - startfoobar - yielding...Getting current value from the generator...foobar - continuedfoobar - yielding...Getting current value from the generator...foobar - continuedfoobar - yielding...Getting current value from the generator...foobar - continuedfoobar - yielding...Getting current value from the generator...foobar - continuedfoobar - yielding...Getting current value from the generator...foobar - continuedfoobar - end
嗯?為什么 Generator created 最先打印出來?這是因為生成器在被使用之前不會執行任何操作。在上例中就是$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 拋出異常。通過這些方法我們不僅可以從生成器中獲取數據,還能向生成器中發送新數據。
讓我們看一個從 Cooperative multitasking using coroutines(強烈推薦閱讀本文)摘取的 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 = [ http://google.com/ , http://github.com/ , http://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實現開發中基于layUI的三級聯動效果的代碼
關于IIS下PHP快三平臺源碼的架設環境的配置過程
以上就是淺談一下PHP生成器的使用方法的詳細內容,PHP教程
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。
新聞熱點
疑難解答