生成器委託
簡單地翻譯官方文檔的描述:
PHP7中,通過生成器委託(yield from),可以將其他生成器、可迭代的對象、數組委託給外層生成器。外層的生成器會先順序 yield 委託出來的值,然後繼續 yield 本身中定義的值。
利用 yield from 可以方便我們編寫比較清晰生成器嵌套,而代碼嵌套調用是編寫複雜系統所必需的。
上例子:
<?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
輸出和上個例子是一樣的。
生成器返回值
如果生成器被迭代完成,或者運行到 return 關鍵字,是會給這個生成器返回值的。
可以有兩種方法獲取這個返回值:
- 使用 $ret = Generator::getReturn() 方法。
- 使用 $ret = yield from Generator() 表達式。
上例子:
<?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) {
;
}
輸出結果就不貼了,想必大家都猜到。
可以看到 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;
});
參考資料
- [1] http://www.php.net/manual/zh/...
- [2] http://www.laruence.com/2015/...
- [3] http://blog.csdn.net/u0101613...