PHP 5.6.0beta1 released

ジェネレータの構文

ジェネレータ関数の見た目はふつうの関数とほぼ同じです。違うのは、値を返すのではなく、 必要なだけ値を yield することです。

ジェネレータ関数が呼ばれると、反復処理が可能なオブジェクトを返します。 このオブジェクトを (foreach ループなどで) 反復させると、 値が必要になるたびに PHP がジェネレータ関数を呼びます。 そして、ジェネレータが値を yield した時点の状態を保存しておき、 次に値が必要になったときにはそこから再開できるようにします。

yield できる値がなくなると、ジェネレータ関数は何もせず単純に終了します。 呼び出し元のコードでは、配列の要素をすべて処理し終えた後のように、そのまま処理が続きます。

注意:

ジェネレータは値を返すことができません。値を返そうとすると、コンパイルエラーになります。 ジェネレータの中で空の return 文を書いても文法上は問題ありませんが、 そこでジェネレータは終了します。

yield キーワード

ジェネレータ関数の肝となるのが yield キーワードです。 最もシンプルな書きかたをすると、yield 文の見た目は return 文とほぼ同じになります。 ただ、return の場合はそこで関数の実行を終了して値を返すのに対して、 yield の場合はジェネレータを呼び出しているループに値を戻して ジェネレータ関数の実行を一時停止します。

例1 値を yield する単純な例

<?php
function gen_one_to_three() {
    for (
$i 1$i <= 3$i++) {
        
// yield を繰り返す間、$i の値が維持されることに注目しましょう
        
yield $i;
    }
}

$generator gen_one_to_three();
foreach (
$generator as $value) {
    echo 
"$value\n";
}
?>

上の例の出力は以下となります。

1
2
3

注意:

内部的には整数の連番のキーが yield する値とペアになり、 配列と同じようになります。

警告

yield を式のコンテキスト (代入文の右辺など) で使うときは、yield 文を括弧で囲む必要があります。 たとえば次のようになります。

$data = (yield $value);

次のように書くと、パースエラーになります。

$data = yield $value;

この構文は、 Generator::send() メソッドと組み合わせて使えます。

値とキーの yield

PHP は、数値添字の配列だけでなく連想配列にも対応しています。ジェネレータも例外ではありません。 先ほどの例のように単なる値を yield するだけでなく、 値と同時にキーも yield することができます。

キーと値のペアを yield する構文は連想配列の定義とよく似ており、次のようになります。

例2 キー/値 のペアの yield

<?php
/*
 * 入力は各フィールドをセミコロンで区切ったものです
 * 最初のフィールドが ID となり、これをキーとして使います
 */

$input = <<<'EOF'
1;PHP;$が大好き
2;Python;インデントが大好き
3;Ruby;ブロックが大好き
EOF;

function 
input_parser($input) {
    foreach (
explode("\n"$input) as $line) {
        
$fields explode(';'$line);
        
$id array_shift($fields);

        yield 
$id => $fields;
    }
}

foreach (
input_parser($input) as $id => $fields) {
    echo 
"$id:\n";
    echo 
"    $fields[0]\n";
    echo 
"    $fields[1]\n";
}
?>

上の例の出力は以下となります。

1:
    PHP
    $が大好き
2:
    Python
    インデントが大好き
3:
    Ruby
    ブロックが大好き
警告

先ほどの例のように値だけを yield するときと同様に、 キー/値 のペアを式のコンテキストで yield するときにも yield 文を括弧で囲む必要があります。

$data = (yield $key => $value);

null 値の yield

何も引数を渡さずに yield を呼ぶと、NULL 値を yield します。キーは自動的に割り振られます。

例3 NULL の yield

<?php
function gen_three_nulls() {
    foreach (
range(13) as $i) {
        yield;
    }
}

var_dump(iterator_to_array(gen_three_nulls()));
?>

上の例の出力は以下となります。

array(3) {
  [0]=>
  NULL
  [1]=>
  NULL
  [2]=>
  NULL
}

参照による yield

ジェネレータ関数は、値を参照として yield することもできます。 関数の結果を参照で返す ときと同じように、関数名の前にアンパサンドを付けます。

例4 参照による値の yield

<?php
function &gen_reference() {
    
$value 3;

    while (
$value 0) {
        yield 
$value;
    }
}

/*
 * $number をループ内で変更していることに注目しましょう。
 * このジェネレータは参照を yield するので、
 * gen_reference() 内の $value が変わります。
 */
foreach (gen_reference() as &$number) {
    echo (--
$number).'... ';
}
?>

上の例の出力は以下となります。

2... 1... 0... 

Generator オブジェクト

ジェネレータ関数を最初に呼んだときには、内部クラス Generator のオブジェクトを返します。 このオブジェクトは Iterator インターフェイスを実装しており、 前進しかできないイテレータオブジェクトと同じように振る舞います。

add a note add a note

User Contributed Notes 3 notes

up
21
Adil lhan (adilmedya at gmail dot com)
11 months ago
For example yield keyword with Fibonacci:

function getFibonacci()
{
    $i = 0;
    $k = 1; //first fibonacci value
    yield $k;
    while(true)
    {
        $k = $i + $k;
        $i = $k - $i;
        yield $k;       
    }
}

$y = 0;

foreach(getFibonacci() as $fibonacci)
{
    echo $fibonacci . "\n";
    $y++;   
    if($y > 30)
    {
        break; // infinite loop prevent
    }
}
up
2
Shumeyko Dmitriy
5 months ago
This is little example of using generators with recursion. Used version of php is 5.5.5
[php]
<?php
define
("DS", DIRECTORY_SEPARATOR);
define ("ZERO_DEPTH", 0);
define ("DEPTHLESS", -1);
define ("OPEN_SUCCESS", True);
define ("END_OF_LIST", False);
define ("CURRENT_DIR", ".");
define ("PARENT_DIR", "..");

function
DirTreeTraversal($DirName, $MaxDepth = DEPTHLESS, $CurrDepth = ZERO_DEPTH)
{
  if ((
$MaxDepth === DEPTHLESS) || ($CurrDepth < $MaxDepth)) {
   
$DirHandle = opendir($DirName);
    if (
$DirHandle !== OPEN_SUCCESS) {
      try{
        while ((
$FileName = readdir($DirHandle)) !== END_OF_LIST) { //read all file in directory
         
if (($FileName != CURRENT_DIR) && ($FileName != PARENT_DIR)) {
           
$FullName = $DirName.$FileName;
            yield
$FullName;
            if(
is_dir($FullName)) { //include sub files and directories
             
$SubTrav = DirTreeTraversal($FullName.DS, $MaxDepth, ($CurrDepth + 1));
              foreach(
$SubTrav as $SubItem) yield $SubItem;
            }
          }
        }
      } finally {
       
closedir($DirHandle);
      }
    }
  }
}

$PathTrav = DirTreeTraversal("C:".DS, 2);
print
"<pre>";
foreach(
$PathTrav as $FileName) printf("%s\n", $FileName);
print
"</pre>";
[/
php]
up
0
denshadewillspam at HOTMAIL dot com
4 days ago
Note that you can't use count() on generators.

/**
 * @return integer[]
 */
function xrange() {
    for ($a = 0; $a < 10; $a++)
    {
        yield $a;
    }
}

function mycount(Traversable $traversable)
{
    $skip = 0;
    foreach($traversable as $skip)
    {
        $skip++;
    }
    return $skip;
}
echo "Count:" . count(xrange()). PHP_EOL;
echo "Count:" . mycount(xrange()). PHP_EOL;
To Top