生成器委托

简单地翻译官方文档的描述:

PHP7中,通过生成器委托(yield from),可以将其他生成器、可迭代的对象、数组委托给外层生成器。外层的生成器会先顺序 yield 委托出来的值,然后继续 yield 本身中定义的值。

利用 yield from 可以方便我们编写比较清晰生成器嵌套,而代码嵌套调用是编写复杂系统所必需的。
上例子:

 1 <?php
 2 function echoTimes($msg, $max) {
 3     for ($i = 1; $i <= $max; ++$i) {
 4         echo "$msg iteration $i\n";
 5         yield;
 6     }
 7 }
 8  
 9 function task() {
10     yield from echoTimes('foo', 10); // print foo ten times
11     echo "---\n";
12     yield from echoTimes('bar', 5); // print bar five times
13 }
14 
15 foreach (task() as $item) {
16     ;
17 }

以上将输出:

 1 foo iteration 1
 2 foo iteration 2
 3 foo iteration 3
 4 foo iteration 4
 5 foo iteration 5
 6 foo iteration 6
 7 foo iteration 7
 8 foo iteration 8
 9 foo iteration 9
10 foo iteration 10
11 ---
12 bar iteration 1
13 bar iteration 2
14 bar iteration 3
15 bar iteration 4
16 bar iteration 5

 

自然,内部生成器也可以接受它的父生成器发送的信息或者异常,因为 yield from 为父子生成器建立一个双向的通道。不多说,上例子:

 1 <?php
 2 function echoMsg($msg) {
 3     while (true) {
 4         $i = yield;
 5         if($i === null){
 6             break;
 7         }
 8         if(!is_numeric($i)){
 9             throw new Exception("Hoo! must give me a number");
10         }
11         echo "$msg iteration $i\n";
12     }
13 }
14 function task2() {
15     yield from echoMsg('foo');
16     echo "---\n";
17     yield from echoMsg('bar');
18 }
19 $gen = task2();
20 foreach (range(1,10) as $num) {
21     $gen->send($num);
22 }
23 $gen->send(null);
24 foreach (range(1,5) as $num) {
25     $gen->send($num);
26 }
27 //$gen->send("hello world"); //try it ,gay

输出和上个例子是一样的。

生成器返回值

如果生成器被迭代完成,或者运行到 return 关键字,是会给这个生成器返回值的。
可以有两种方法获取这个返回值:

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

上例子:

 1 <?php
 2 function echoTimes($msg, $max) {
 3     for ($i = 1; $i <= $max; ++$i) {
 4         echo "$msg iteration $i\n";
 5         yield;
 6     }
 7     return "$msg the end value : $i\n";
 8 }
 9 
10 function task() {
11     $end = yield from echoTimes('foo', 10);
12     echo $end;
13     $gen = echoTimes('bar', 5);
14     yield from $gen;
15     echo $gen->getReturn();
16 }
17 
18 foreach (task() as $item) {
19     ;
20 }

输出结果就不贴了,想必大家都猜到。

可以看到 yield from 和 return 结合使得 yield 的写法更像平时我们写的同步模式的代码了,毕竟,这就是 PHP 出生成器特性的原因之一呀。

一个非阻塞的web服务器

时间回到2015年,鸟哥博客上转载的一篇《 在PHP中使用协程实现多任务调度》。文章介绍了PHP5 的迭代生成器,协程,并实现了一个简单的非阻塞 web 服务器。(链接见文末引用)

现在我们利用 PHP7 中的这两个新特性重写这个 web 服务器,只需要 100 多行代码。

代码如下:

  1 <?php
  2 
  3 class CoSocket
  4 {
  5     protected $masterCoSocket = null;
  6     public $socket;
  7     protected $handleCallback;
  8     public $streamPoolRead = [];
  9     public $streamPoolWrite = [];
 10 
 11     public function __construct($socket, CoSocket $master = null)
 12     {
 13         $this->socket = $socket;
 14         $this->masterCoSocket = $master ?? $this;
 15     }
 16 
 17     public function accept()
 18     {
 19         $isSelect = yield from $this->onRead();
 20         $acceptS = null;
 21         if ($isSelect && $as = stream_socket_accept($this->socket, 0)) {
 22             $acceptS = new CoSocket($as, $this);
 23         }
 24         return $acceptS;
 25     }
 26 
 27     public function read($size)
 28     {
 29         yield from $this->onRead();
 30         yield ($data = fread($this->socket, $size));
 31         return $data;
 32     }
 33 
 34     public function write($string)
 35     {
 36         yield from $this->onWriter();
 37         yield fwrite($this->socket, $string);
 38     }
 39 
 40     public function close()
 41     {
 42         unset($this->masterCoSocket->streamPoolRead[(int)$this->socket]);
 43         unset($this->masterCoSocket->streamPoolWrite[(int)$this->socket]);
 44         yield ($success = @fclose($this->socket));
 45         return $success;
 46     }
 47 
 48     public function onRead($timeout = null)
 49     {
 50         $this->masterCoSocket->streamPoolRead[(int)$this->socket] = $this->socket;
 51         $pool = $this->masterCoSocket->streamPoolRead;
 52         $rSocks = [];
 53         $wSocks = $eSocks = null;
 54         foreach ($pool as $item) {
 55             $rSocks[] = $item;
 56         }
 57         yield ($num = stream_select($rSocks, $wSocks, $eSocks, $timeout));
 58         return $num;
 59     }
 60 
 61     public function onWriter($timeout = null)
 62     {
 63         $this->masterCoSocket->streamPoolWrite[(int)$this->socket] = $this->socket;
 64         $pool = $this->masterCoSocket->streamPoolRead;
 65         $wSocks = [];
 66         $rSocks = $eSocks = null;
 67         foreach ($pool as $item) {
 68             $wSocks[] = $item;
 69         }
 70         yield ($num = stream_select($rSocks, $wSocks, $eSocks, $timeout));
 71         return $num;
 72     }
 73 
 74     public function onRequest()
 75     {
 76         /** @var self $socket */
 77         $socket = yield from $this->accept();
 78         if (empty($socket)) {
 79             return false;
 80         }
 81         $data = yield from $socket->read(8192);
 82         $response = call_user_func($this->handleCallback, $data);
 83         yield from $socket->write($response);
 84         return yield from $socket->close();
 85     }
 86 
 87     public static function start($port, callable $callback)
 88     {
 89         echo "Starting server at port $port...\n";
 90         $socket = @stream_socket_server("tcp://0.0.0.0:$port", $errNo, $errStr);
 91         if (!$socket) throw new Exception($errStr, $errNo);
 92         stream_set_blocking($socket, 0);
 93         $coSocket = new self($socket);
 94         $coSocket->handleCallback = $callback;
 95         function gen($coSocket)
 96         {
 97             /** @var self $coSocket */
 98             while (true) yield from $coSocket->onRequest();
 99         }
100         foreach (gen($coSocket) as $item){};
101     }
102 }
103 
104 CoSocket::start(8000, function ($data) {
105     $response = <<<RES
106 HTTP/1.1 200 OK
107 Content-Type: text/plain
108 Content-Length: 12
109 Connection: close
110 
111 hello world!
112 RES;
113     return $response;
114 });

 

posted on 2019-05-23 15:45  鸥海  阅读(787)  评论(0编辑  收藏  举报