[php: Generator] count function as generator sample. - #php
PHP 5.5 より実装された「独自イテレータを簡単に実装できる」機能で、大体どの言語にも存在する。通常の関数は return を一度だけ返すが、ジェネレータオブジェクトはイテレーションの中で実行結果を何度も yield する。端的に言うと 関数の動きを一時停止して、再度再開することが出来る 。
SQL のリザルトセットを一度配列に格納して foreach ループ、みたいな処理は 配列の大きさが一定を超えるとメモリ超過でコケる 。このような場合に 「実行結果をコレクションしておきイテレートされたときに 1 行ずつ yield する」 ようなジェネレータを作成すると、最初にクエリ実行したあとは 1 レコードずつ配列を返却するような機能を実装できる ( = SQL 実行結果のストリーミング ) 。
/**
* Generator: Fetch users.
*/
function fetchUsers(): \Generator
{
$dbh = new PDO(/* ~ */);
$stmt = $dbh->prepare("SELECT * FROM users");
$stmt->execute();
while ($user = $stmt->fetch(PDO::FETCH_OBJ)) {
yield $user;
}
}
// Iterate generator without huge array.
foreach (fetchUsers() as $user) {
print_r($user);
}
Query オブジェクトの SQL 実行は foreach による初回イテレート時一度だけ ... なんて省メモリ!!!
class LogsTable extends Table
{
public function fetch(): \Generator
{
$logs = $this->find('recent');
foreach ($logs as $log) {
yield $log;
}
}
}
// Using.
class LogsController extends AppController
{
public function print()
{
foreach ($this->Logs->fetch() as $log) {
print_r($log->toArray());
}
}
}
<?php
/**
* Count generator.
*
* @param int $start
* @param int $step
* @param int|null $limit
* @return Generator
*/
function countGenerator(int $start=0, int $step=1, int $limit=null): \Generator
{
if (empty($limit)) for ($i = $start; ; $i += $step) yield $i;
else for ($i = $start; $i <= $limit; $i += $step) yield $i;
}
// Create generator as iterable object.
// $counts = countGenerator(0, 1);
$counts = countGenerator(0, 1, 5);
// Call yield on loop.
foreach ($counts as $count) {
echo $count;
if ($count === 100) break;
}