Délégué du générateur
Traduisez simplement la description de la documentation officielle :
PHP7, via le délégué du générateur (yield from), Vous pouvez déléguez d'autres générateurs, objets itérables et tableaux à des générateurs externes. Le générateur externe produira d’abord la valeur déléguée de manière séquentielle, puis continuera à produire la valeur définie en lui-même.
L'utilisation de rendement from peut nous permettre d'écrire plus facilement une imbrication de générateur plus claire, et les appels d'imbrication de code sont nécessaires pour écrire des systèmes complexes.
L'exemple ci-dessus :
<?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) { ; }
Ce qui précède affichera :
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
Naturellement, le générateur interne peut également accepter des informations ou des exceptions envoyées par son générateur parent, car le rendement de établit un canal bidirectionnel pour les générateurs parent et enfant. Sans plus tarder, voici un exemple :
<?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
Le résultat est le même que l’exemple précédent.
Valeur de retour du générateur
Si le générateur est itéré ou exécute le mot-clé return, le générateur renverra une valeur.
Il existe deux façons d'obtenir cette valeur de retour :
- Utilisez la méthode $ret = Generator::getReturn().
- Utilisez l'expression $ret = rendement de Generator().
L'exemple ci-dessus :
<?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) { ; }
Le résultat de la sortie ne sera pas affiché, tout le monde a dû l'avoir deviné.
Vous pouvez voir que la combinaison de rendement depuis et retour rend la façon d'écrire rendement plus proche du code en mode synchrone que nous écrivons habituellement. Après tout, c'est l'une des raisons pour lesquelles PHP dispose de la fonctionnalité générateur.
Un serveur Web non bloquant
En 2015, un article "Utiliser des coroutines pour implémenter la planification multi-tâches en PHP" a été republié sur le blog de Brother Niao. L'article présente le générateur itératif de PHP5, la coroutine, et implémente un simple serveur Web non bloquant. (Voir le lien en fin d'article)
Nous utilisons désormais ces deux nouvelles fonctionnalités de PHP7 pour réécrire ce serveur web, qui ne nécessite que plus de 100 lignes de code.
Le code est le suivant :
<?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; });