PHP5.5で追加されるyieldについて調べた。
とりあえずインストール。
cd /tmp git clone https://git.php.net/repository/php-src.git cd php-src/ ./buildconf ./configure --prefix=/tmp --disable-all make make install
ジェネレータオブジェクトとは
ぱっと見、普通のfunctionなのかジェネレータオブジェクトを返す関数なのか解らないけど、
メソッド内でyieldを使っていると、functionはジェネレータオブジェクトを返すらしい。
ジェネレータオブジェクトの情報をダンプしてみる。
クラス名はGenerator。
親クラスなし。
実装されているインターフェイスはIteratorのみ。
実装されているインターフェイスで定義されていないメソッドは、send()と__wakeup()。
ソース
<?php function gentest() { yield 1; } $g = gentest(); // class name $g_class = get_class($g); echo "class name:\n"; echo " {$g_class}\n\n"; // parent $p_class = get_parent_class($g); echo "parent class:\n"; echo " {$p_class}\n\n"; // interfaces echo "implemented interfaces:\n"; $interfaces = get_declared_interfaces(); foreach ($interfaces as $interface) { if ($g instanceof $interface) { echo " {$interface}\n"; } } echo "\n"; // methods $methods = get_class_methods($g); echo "methods:\n"; foreach ($methods as $method) { echo " {$method}\n"; }
実行結果
class name: Generator parent class: implemented interfaces: Traversable Iterator methods: rewind valid current key next send __wakeup
yieldの挙動
1回目にジェネレータオブジェクトをIteratorとして実行した時に初めてfunction内の1行目が実行されるらしい。
$g->send($arg)を使わず$g->next()を使うとyieldの戻り値はNULLとなるらしい。
ソース
<?php function gentest() { echo "gentest start\n"; $ret = 1; $arg = yield $ret++; var_dump($arg); $arg = yield $ret++; var_dump($arg); $arg = yield $ret++; var_dump($arg); echo "gentest end\n"; } $g = gentest(); var_dump($g); echo "-----\n"; foreach ($g as $i) { var_dump($i); }
実行結果
object(Generator)#1 (0) { } ----- gentest start int(1) int(2) int(3) gentest end
yieldの挙動を更に詳しく
今度は手動でイテレーションさせてみる。
rewind()を実行して初めて関数が実行されるらしい。
yieldで戻ってきて、next()もしくはsend()で続きが実行される。
ソース
<?php function gentest() { echo "gentest start\n"; $ret = 1; $arg = (yield $ret++); var_dump($arg); $arg = (yield $ret++); var_dump($arg); $arg = (yield $ret++); var_dump($arg); echo "gentest end\n"; } $g = gentest(); // foreach echo "-----\n"; echo "rewind start\n"; $g->rewind(); echo "rewind end\n"; while ($g->valid()) { echo "-----\n"; var_dump($g->key()); var_dump($g->current()); echo "next start\n"; $g->next(); echo "next end\n"; }
実行結果
----- rewind start gentest start rewind end ----- int(0) int(1) next start NULL next end ----- int(1) int(2) next start NULL next end ----- int(2) int(3) next start NULL gentest end next end
2回回すと死ぬ
ソース
<?php function gentest() { yield 1; } $g = gentest(); foreach ($g as $i) {} foreach ($g as $i) {}
実行結果
Fatal error: Uncaught exception 'Exception' with message 'Cannot traverse an already closed generator' in /private/tmp/yield_test3.php:11 Stack trace: #0 /private/tmp/yield_test3.php(11): unknown() #1 {main} thrown in /private/tmp/yield_test3.php on line 11