Generator Delegate
에 있는 생성기의 새로운 기능 공식 문서의 설명을 간단히 번역하세요:
PHP7, 생성기 위임(yield from)을 통해 다음을 수행할 수 있습니다. 다른 생성기, 반복 가능한 객체 및 배열을 외부 생성기에 위임합니다. 외부 생성기는 먼저 위임된 값을 순차적으로 생성한 다음 자체적으로 정의된 값을 계속해서 생성합니다.
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
출력은 이전 예와 동일합니다.
생성기 반환 값
생성기가 반복되거나 반환 키워드로 실행되면 생성기가 값을 반환합니다.
이 반환 값을 얻는 방법에는 두 가지가 있습니다.
- $ret = Generator::getReturn() 메서드를 사용하세요.
- $ret = Generator() 표현식의 Yield를 사용하세요.
위의 예:
<?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에 생성기 기능이 있는 이유 중 하나입니다.
비차단 웹 서버
2015년에 "코루틴을 사용하여 PHP에서 다중 작업 예약 구현"이라는 기사가 Brother Niao의 블로그에 다시 게시되었습니다. 이 기사에서는 PHP5의 반복 생성기인 코루틴을 소개하고 간단한 비차단 웹 서버를 구현합니다. (기사 마지막에 있는 링크를 참조하세요)
이제 우리는 PHP7의 두 가지 새로운 기능을 사용하여 이 웹 서버를 다시 작성하는데, 이는 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; });