PHP7中生成器的新特性 yield-from && return-values

生成器委託

簡單地翻譯官方文檔的描述:php

PHP7中,經過生成器委託(yield from),能夠將其餘生成器、可迭代的對象、數組委託給外層生成器。外層的生成器會先順序 yield 委託出來的值,而後繼續 yield 自己中定義的值。html

利用 yield from 能夠方便咱們編寫比較清晰生成器嵌套,而代碼嵌套調用是編寫複雜系統所必需的。
上例子:web

<?php
function echoTimes($msg, $max) {
    for ($i = 1; $i <= $max; ++$i) {
        echo "$msg iteration $i\n";
        yield;
    }
}
 
function task() {
    yield from echoTimes('foo', 10); // print foo ten times
    echo "---\n";
    yield from echoTimes('bar', 5); // print bar five times
}

foreach (task() as $item) {
    ;
}

以上將輸出:數組

foo iteration 1
foo iteration 2
foo iteration 3
foo iteration 4
foo iteration 5
foo iteration 6
foo iteration 7
foo iteration 8
foo iteration 9
foo iteration 10
---
bar iteration 1
bar iteration 2
bar iteration 3
bar iteration 4
bar iteration 5

天然,內部生成器也能夠接受它的父生成器發送的信息或者異常,由於 yield from 爲父子生成器創建一個雙向的通道。很少說,上例子:服務器

<?php
function echoMsg($msg) {
    while (true) {
        $i = yield;
        if($i === null){
            break;
        }
        if(!is_numeric($i)){
            throw new Exception("Hoo! must give me a number");
        }
        echo "$msg iteration $i\n";
    }
}
function task2() {
    yield from echoMsg('foo');
    echo "---\n";
    yield from echoMsg('bar');
}
$gen = task2();
foreach (range(1,10) as $num) {
    $gen->send($num);
}
$gen->send(null);
foreach (range(1,5) as $num) {
    $gen->send($num);
}
//$gen->send("hello world"); //try it ,gay

輸出和上個例子是同樣的。socket

生成器返回值

若是生成器被迭代完成,或者運行到 return 關鍵字,是會給這個生成器返回值的。
能夠有兩種方法獲取這個返回值:tcp

  1. 使用 $ret = Generator::getReturn() 方法。
  2. 使用 $ret = yield from Generator() 表達式。

上例子:this

<?php
function echoTimes($msg, $max) {
    for ($i = 1; $i <= $max; ++$i) {
        echo "$msg iteration $i\n";
        yield;
    }
    return "$msg the end value : $i\n";
}

function task() {
    $end = yield from echoTimes('foo', 10);
    echo $end;
    $gen = echoTimes('bar', 5);
    yield from $gen;
    echo $gen->getReturn();
}

foreach (task() as $item) {
    ;
}

輸出結果就不貼了,想必你們都猜到。.net

能夠看到 yield from 和 return 結合使得 yield 的寫法更像平時咱們寫的同步模式的代碼了,畢竟,這就是 PHP 出生成器特性的緣由之一呀。翻譯

一個非阻塞的web服務器

時間回到2015年,鳥哥博客上轉載的一篇《 在PHP中使用協程實現多任務調度》。文章介紹了PHP5 的迭代生成器,協程,並實現了一個簡單的非阻塞 web 服務器。(連接見文末引用)

如今咱們利用 PHP7 中的這兩個新特性重寫這個 web 服務器,只須要 100 多行代碼。

代碼以下:

<?php

class CoSocket
{
    protected $masterCoSocket = null;
    public $socket;
    protected $handleCallback;
    public $streamPoolRead = [];
    public $streamPoolWrite = [];

    public function __construct($socket, CoSocket $master = null)
    {
        $this->socket = $socket;
        $this->masterCoSocket = $master ?? $this;
    }

    public function accept()
    {
        $isSelect = yield from $this->onRead();
        $acceptS = null;
        if ($isSelect && $as = stream_socket_accept($this->socket, 0)) {
            $acceptS = new CoSocket($as, $this);
        }
        return $acceptS;
    }

    public function read($size)
    {
        yield from $this->onRead();
        yield ($data = fread($this->socket, $size));
        return $data;
    }

    public function write($string)
    {
        yield from $this->onWriter();
        yield fwrite($this->socket, $string);
    }

    public function close()
    {
        unset($this->masterCoSocket->streamPoolRead[(int)$this->socket]);
        unset($this->masterCoSocket->streamPoolWrite[(int)$this->socket]);
        yield ($success = @fclose($this->socket));
        return $success;
    }

    public function onRead($timeout = null)
    {
        $this->masterCoSocket->streamPoolRead[(int)$this->socket] = $this->socket;
        $pool = $this->masterCoSocket->streamPoolRead;
        $rSocks = [];
        $wSocks = $eSocks = null;
        foreach ($pool as $item) {
            $rSocks[] = $item;
        }
        yield ($num = stream_select($rSocks, $wSocks, $eSocks, $timeout));
        return $num;
    }

    public function onWriter($timeout = null)
    {
        $this->masterCoSocket->streamPoolWrite[(int)$this->socket] = $this->socket;
        $pool = $this->masterCoSocket->streamPoolRead;
        $wSocks = [];
        $rSocks = $eSocks = null;
        foreach ($pool as $item) {
            $wSocks[] = $item;
        }
        yield ($num = stream_select($rSocks, $wSocks, $eSocks, $timeout));
        return $num;
    }

    public function onRequest()
    {
        /** @var self $socket */
        $socket = yield from $this->accept();
        if (empty($socket)) {
            return false;
        }
        $data = yield from $socket->read(8192);
        $response = call_user_func($this->handleCallback, $data);
        yield from $socket->write($response);
        return yield from $socket->close();
    }

    public static function start($port, callable $callback)
    {
        echo "Starting server at port $port...\n";
        $socket = @stream_socket_server("tcp://0.0.0.0:$port", $errNo, $errStr);
        if (!$socket) throw new Exception($errStr, $errNo);
        stream_set_blocking($socket, 0);
        $coSocket = new self($socket);
        $coSocket->handleCallback = $callback;
        function gen($coSocket)
        {
            /** @var self $coSocket */
            while (true) yield from $coSocket->onRequest();
        }
        foreach (gen($coSocket) as $item){};
    }
}

CoSocket::start(8000, function ($data) {
    $response = <<<RES
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 12
Connection: close

hello world!
RES;
    return $response;
});

參考資料

相關文章
相關標籤/搜索