サーチ…
なぜ発電機を使用するのですか?
ジェネレータは、後で反復処理を行うために大きなコレクションを生成する必要がある場合に便利です。これらは、 Iteratorを実装するクラスを作成するための簡単な代替手段です。
たとえば、以下の関数を考えてみましょう。
function randomNumbers(int $length)
{
$array = [];
for ($i = 0; $i < $length; $i++) {
$array[] = mt_rand(1, 10);
}
return $array;
}
このすべての関数は、乱数で満たされた配列を生成します。これを使用するにはrandomNumbers(10)
実行します。これは10個の乱数の配列をrandomNumbers(10)
ます。 100万の乱数を生成する場合はどうなりますか? randomNumbers(1000000)
は、私たちのためにそれを行いますが、メモリを犠牲にして行います。アレイに格納される100万の整数は、約33メガバイトのメモリを使用します。
$startMemory = memory_get_usage();
$randomNumbers = randomNumbers(1000000);
echo memory_get_usage() - $startMemory, ' bytes';
これは、一度に1つではなく、100万の乱数全体が生成されて一度に返されるためです。発電機はこの問題を解決する簡単な方法です。
ジェネレータを使ってrandomNumbers()を書き直す
私たちのrandomNumbers()
関数は、ジェネレータを使うためにrandomNumbers()
ことができます。
<?php
function randomNumbers(int $length)
{
for ($i = 0; $i < $length; $i++) {
// yield tells the PHP interpreter that this value
// should be the one used in the current iteration.
yield mt_rand(1, 10);
}
}
foreach (randomNumbers(10) as $number) {
echo "$number\n";
}
ジェネレータを使用すると、関数から返される乱数のリスト全体を構築する必要がなくなり、使用メモリが大幅に少なくなります。
ジェネレータで大きなファイルを読む
ジェネレータの一般的な使用例の1つは、ディスクからファイルを読み取り、その内容を反復することです。以下は、CSVファイルを反復処理できるクラスです。このスクリプトのメモリ使用量は非常に予測可能で、CSVファイルのサイズによって変動することはありません。
<?php
class CsvReader
{
protected $file;
public function __construct($filePath) {
$this->file = fopen($filePath, 'r');
}
public function rows()
{
while (!feof($this->file)) {
$row = fgetcsv($this->file, 4096);
yield $row;
}
return;
}
}
$csv = new CsvReader('/path/to/huge/csv/file.csv');
foreach ($csv->rows() as $row) {
// Do something with the CSV row.
}
収穫のキーワード
yield
文はreturn文と似ていますが、代わりに関数の実行を停止して返す代わりにyieldがGeneratorオブジェクトを返し、 ジェネレータ関数の実行を一時停止します。
以下は、ジェネレータとして記述された範囲関数の例です:
function gen_one_to_three() {
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $i;
}
}
この関数は、 var_dump
の出力を検査してGeneratorオブジェクトを返すことがわかります。
var_dump(gen_one_to_three())
# Outputs:
class Generator (0) {
}
値を生み出す
Generatorオブジェクトは、配列のように繰り返し処理できます。
foreach (gen_one_to_three() as $value) {
echo "$value\n";
}
上の例は次のように出力されます:
1
2
3
鍵で値を得る
値を生成するだけでなく、キー/値のペアを生成することもできます。
function gen_one_to_three() {
$keys = ["first", "second", "third"];
for ($i = 1; $i <= 3; $i++) {
// Note that $i is preserved between yields.
yield $keys[$i - 1] => $i;
}
}
foreach (gen_one_to_three() as $key => $value) {
echo "$key: $value\n";
}
上の例は次のように出力されます:
first: 1
second: 2
third: 3
send() - 関数を使用して値をジェネレータに渡す
ジェネレータは高速にコード化されており、多くの場合、イテレータの実装が大幅に簡素化されています。速い実装では、ジェネレータの生成を停止する必要がある場合や、何か他のものを生成する必要がある場合には、制御が少し不足します。ただし、これはsend()
関数の使用で実現でき、リクエスト関数がループごとにパラメータをジェネレータに送信できるようにします。
//Imagining accessing a large amount of data from a server, here is the generator for this:
function generateDataFromServerDemo()
{
$indexCurrentRun = 0; //In this example in place of data from the server, I just send feedback everytime a loop ran through.
$timeout = false;
while (!$timeout)
{
$timeout = yield $indexCurrentRun; // Values are passed to caller. The next time the generator is called, it will start at this statement. If send() is used, $timeout will take this value.
$indexCurrentRun++;
}
yield 'X of bytes are missing. </br>';
}
// Start using the generator
$generatorDataFromServer = generateDataFromServerDemo ();
foreach($generatorDataFromServer as $numberOfRuns)
{
if ($numberOfRuns < 10)
{
echo $numberOfRuns . "</br>";
}
else
{
$generatorDataFromServer->send(true); //sending data to the generator
echo $generatorDataFromServer->current(); //accessing the latest element (hinting how many bytes are still missing.
}
}
この出力結果: