PHP yield 協程 生成器用法探究

寫在前面

這篇文章,要和你們探討的是 PHP yield 在 生成器用法,不帶 foreachfor, while 循環的那種。就討論 yield 將一個函數變成爲生成器的用法。php

關於yield 特性,是在開發 PHP5 時被提上日程,PHP5.5 版本正式加入。git

關於yield的使用,我看到大部分文章都停留在,使用yield如何在foreach中穿出數據,今天想給你們講講 生成器 全部語法。express

官網講解

生成器容許你在 foreach 代碼塊中寫代碼來迭代一組數據而不須要在內存中建立一個數組, 那會使你的內存達到上限,或者會佔據可觀的處理時間。相反,你能夠寫一個生成器函數,就像一個普通的自定義函數同樣, 和普通函數只返回一次不一樣的是, 生成器能夠根據須要 yield 屢次,以便生成須要迭代的值。數組

看了下官網對他講解:php.net 生成器語法 . 每一個字都認識,但彷佛仍是體會到它講的內涵。官網咱們主要看兩部份內容:bash

  1. yield 的語法。php7

  2. 使用例子。函數

先說語法, yield 的左邊是一個賦值語句,右邊能夠是值(也但是表達式) 。而yield 會先執行右邊的表達式,並把值value送到生成器外面。當生成器收到值後,會執行yield左邊的語句,賦值給data.oop

<?php
function func() {
    $data = (yield [$express]);
}
複製代碼

語法講完了,估計你們仍是有些懵,那就看看官網下面代碼例子吧,我看裏面例子良莠不齊。測試

注意yield 外面包的這一層括號,若是是在php5.5,右側express的優先級是判斷,可能會比左側data的賦值語句低的。因此在php5用yield,yield 右邊是可運行表達式,左側須要接受返回並賦值,那麼這個括號是有必要的。在php7不會有這個問題。ui

經過例子來了解它

不管是學 人類語言,計算機語言,都是模仿開始

對於一個用人類語言來描述,都不那麼明晰時,因此那就經過例子告訴你它能作什麼,不能作什麼。

相關代碼,我放到gitee了,但願你能複製到你本地運行下,親自運行感覺下,有助於了理解接下來的內容。

git clone gitee.com/xupaul/PHP-…

怎樣才能產生 Generator

先定義一個函數,在函數內 寫個 yield 關鍵詞,將這個函數調用賦值給一個變量。一個生成器就產生了。

代碼 /php-yield-test/yieldFunctions.php 是生成器按照不一樣語法組合定義了多個生成器。

測試代碼 /php-yield-test/whatIsGenerator.php,用來檢查哪些函數能構成生成器,哪些不能。運行結果以下

test result

  1. 函數內必須有 yield 關鍵詞,函數能夠是全劇函數,或者類的方法。
  2. 哪怕 yield 確定不會被執行,也會產生生成器。見:yield_func4
  3. 光禿禿 的 yield 關鍵詞就行(不向外送出,不處理外面的輸入)。見: yield_func2
  4. 函數內使用 生成器 並不能讓本身也成爲生成器,見:yield_func5
  5. eval函數中直接運行 yield 會報錯, 見:yield_func11
  6. 調用生成器函數,不限於調用形式,除開經常使用的直接調用,call_user_func, 經過函數名(字符串)動態調用 也能正常產生生產器的。

是的,函數內有沒有foreach,while,for 語句都不是關鍵,關鍵是 yield. 生成器的類型判斷用 $gen instanceof Generator

生成器的函數

Generator 對象是從 generators返回的.

Generator 對象不能經過 new 實例化.

摘自 php.net generator

看着以上方法,是不想起了Iterator, 他們的確很像。同時注意,官網zh語言版本的文檔沒有索引方法getReturn,訪問也是404。文檔以en版爲準,ch作參考。

以上就是生成器全部的方法,咱們一個個來看。

測試方法代碼 /php-yield-test/generatorMothod.php, 這裏面對每一個方法都有使用舉例,運行結果以下。

run result 2

run result 3

好接下來對舉例作個一一講解。

Generator::current

  • 返回當前產生的值
<?php
function yield_func() {
    yield 12;
    return 'a';
}

$gen = yield_func();
$re = $gen->current();
echo 'current return : ' . $re;
複製代碼

輸出:

current return : 12
複製代碼

看到 php-yield-test/generatorMothod.php 代碼。

經過第一個代碼事例,可得,對一個generator調用current方法,纔算真正開始執行。執行到yield爲止。若是不能命中yield,則執行到函數結束。

非generoator會立馬執行並獲得結果,而非一個生成器對象。

經過例子2,調用current一次,兩次呢,第一次能夠看到代碼執行日誌,第二次,只是把上一次的結果返回給咱們而已,並非讓該生成器從新執行。

經過例子1,調用該函數還會獲取到返回值,返回的內容就是 yield 表達式左邊的內容。若是表達式無內容,則是NULL.

Generator::send

  • 向生成器yield點中傳入一個值,並返回下一次current值。
<?php
function yield_func() {
    $data = yield 12;
    echo 'get yield data: ' . $data;
    return 'a';
}

$gen = yield_func();
$re = $gen->current();
$gen->send(32);
複製代碼

輸出:

get yield data: 32
複製代碼

例子3,是一個current,send的常規調用。調用current代碼運行yield等到用戶send輸入參數。接收到輸入後,繼續運行。current可以接收到yield彈出的值,send返回值爲空。

例子4,直接調用send,至關於調用current,send。不過current的返回值,並不會經過send傳給用戶。

例子21中,能夠看到直接調用send(1),會運行生成器,並向第一個yield處輸入1,繼續運行至下一個yield的返回值value。因此,$gen->send(2),和 $gen->current() 結果都是同一個值。

也就是說:跳過current,直接調用send,會丟失第一次yield的彈出值。

Generator::next

  • 跳過中斷,並讓生成器繼續執行
<?php
function yield_func() {
    echo 'run to code line: ' . __LINE__ . PHP_EOL;
    yield;
    echo 'run to code line: ' . __LINE__ . PHP_EOL;
    return $result;
}

$gen = yield_func();
$gen->current();
echo 'current called' . PHP_EOL;
$gen->next();
複製代碼

輸出:

run to code line: 4
current called
run to code line: 6
複製代碼

例子5,這是一個較爲常規的調用,調用current代碼運行yield等到用戶輸入,這是調用next跳過,讓代碼繼續運行。

例子6,直接調用next,至關於調用currentnext。並且經過最後打印$result, 咱們發現怎麼有點像在調用 $gen->send(NULL);

Generator::rewind

  • 重置迭代器
<?php
function yield_func() {
    echo 'run to code line: ' . __LINE__ . PHP_EOL;
    $result = yield 12;
    echo 'run to code line: ' . __LINE__ . PHP_EOL;
}

$gen = yield_func();
echo 'call yield_func rewind ' . PHP_EOL;
$gen->rewind();
複製代碼

輸出:

call yield_func rewind 
run to code line: 4
複製代碼

例子7,8 中,發現調用該方法,會致使隱式調用current

例子9 中,發如今執行過一個yield代碼段後,再次調用該方法,會致使報錯(哪怕該 生成器已結束)。

Generator::throw

  • 向生成器中拋入一個異常
<?php
function yield_func() {
    try {
        $re = yield 'exception';
    } catch (Exception $e) {
        echo 'catched exception msg: ' .$e->getMessage();
    }
}

$gen = yield_func();
$gen->throw(new \Exception('new yield exception'));
複製代碼

輸出:

catched exception msg: new yield  exception
複製代碼

經過以上簡單的例子可得,throw 就是讓yield這行代碼產生異常,讓外面的try catch 捕獲咱們生成的那個異常。

例子11中,構造生成器,並調用current方法,運行到yield處,再調用throw,就能捕獲到異常。

例子12中,當調用send方法,跳過函數內yield代碼時,再調用throw傳入異常,就無法捕獲了。

Generator::valid

  • 檢查迭代器是否被關閉
<?php
function yield_func() {
    yield 12;
    return 'a';
}

$gen = yield_func();
$gen->send(1);
$check = $gen->valid();
echo 'the generator valid ? ' . intval($check);
複製代碼

輸出:

the generator valid ? 0
複製代碼

例子12中,發現current被隱式調用。

例子13中,可得,當生成器運行到yield代碼段時,用valid函數檢查,都會返回true

因此,別問我是否已運行,問就是運行。該方法用來獲取是否關閉狀態,不是 是否運行狀態!運行到底,運行到return就是 關閉狀態。

Generator::key

  • 返回當前產生的鍵
<?php
function yield_func() {
    yield 1 => 'abc';
}

$gen = yield_func();
echo 'value is :' . $gen->current() . PHP_EOL;
echo 'key is: ' . $gen->key() . PHP_EOL;
複製代碼

輸出:

value is :abc
key is: 1
複製代碼

從以上例子中,可得yield可顯示設置返回的key.

例子15 中,發現key的分發規律和PHP數組鍵值發放策略是差很少的,默認從0開始,未指定則是以上一個數字key+1做爲當前的key.

例子16 中,咱們又發現current被隱式調用。

Generator::__wakeup

  • Generator::__wakeup — 序列化回調
<?php
function yield_func() {
    yield 1 => 'abc';
}
$gen = yield_func();
try {
$ser = serialize($gen);
} catch (\Exception $e) {
	print_r($e->getMessage());
}
複製代碼

輸出:

Serialization of 'Generator' is not allowed
複製代碼

這是一個魔術方法,見 PHP 魔術方法,也就是說 生成器 不能被序列化成一個字符串。

例子17就不用說了,看下例子18,看樣子序列化成功了。也就是說一個生成器作爲一個方法能夠被序列化,當函數變成生成器時,就不能被序列化了。

Generator::getReturn

<?php
function yield_func() {
    yield 1 => 'abc';
	return 32;
}

$gen = yield_func();
$gen->send(0);
echo 'call yield_func return, and get: ' . $gen->getReturn();
複製代碼

輸出:

call yield_func return, and get: 32
複製代碼

該函數就是獲取生成器最後的返回值。若是沒有return語句,或者沒有執行到return語句,調用該函數獲得的就是NULL。

例子19 可得,getReturn 可以獲取到生成器最後的返回值。

例子1九、20 可得,當生成器沒有執行到return語句,或者沒有執行到最後時,調用getReturn是會致使報錯。

綜上所述

到這裏,咱們就發現rewind,next__wakeup 這兩個函數感受沒啥叼用呢,爲啥還存在呢,由於Generator繼承Iterator,天然就有了rewind, next方法,PHP 雖然支持方法覆蓋,但子類的訪問修飾符 不能縮緊,因此Generator只能重寫這兩個方法。 __wakeup 繼承自 stdClass

狀態轉換

看圖:

php yield statechart
PHP yield 生命週期圖

畫了兩個狀態轉換圖,上面的要細緻,繁複一點。下面的精簡版,便於快速理解。

總結

以上就是關於 PHP 生成器的基礎內容,但願你看了後對它有更進一步認識。下一講,咱們手把手一塊兒來作一個任務調度器,實戰一下。

有問題歡迎提問,謝謝你們!

沒人比我更懂
相關文章
相關標籤/搜索