ジェネレータとイテレータ
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 62/91。
ジェネレータは、完全な Iterator インターフェースを実装することなく、イテレータを作成するための簡単な方法を提供します。ジェネレータ関数は、return の代わりに yield を使用して値を1つずつ生成し、各値の間で実行を一時停止します。
以下は、一連の数値を生成する基本的なジェネレーターです。
<?php
function countTo(int $max): Generator {
for ($i = 1; $i <= $max; $i++) {
yield $i;
}
}
foreach (countTo(3) as $number) {
echo "$number\n";
}
出力:
1
2
3主な利点はメモリ効率です。メモリ内に配列全体を構築する代わりに、ジェネレータはオンデマンドで値を生成します。これは大規模なデータセットを扱う際に非常に重要です。100万件のレコードを処理するジェネレータも、10件を処理するものと同じメモリしか使用しません。
ジェネレータは、キーと値のペアを yield することもできます:
<?php
function userGenerator(): Generator {
yield 'admin' => 'Alice';
yield 'editor' => 'Bob';
}
foreach (userGenerator() as $role => $name) {
echo "$role: $name\n";
}
出力:
admin: Alice
editor: Bob独自のクラスを foreach で反復可能にするには、Iterator インターフェースを実装します。しかし、ほとんどの場合、ジェネレータの方がボイラープレートコードが少なく、よりクリーンな解決策を提供します。これらは、クラスがすべての項目を一度にメモリに読み込むことなく、項目のコレクションを公開する必要がある OOP において特に有用です。
チャレンジ
簡単大規模なコレクションを一度にすべてメモリに読み込むことなく、効率的に反復処理を行うために、ジェネレータを使用した製品在庫管理システムを構築しましょう。製品を保存し、さまざまな方法でそれらを反復処理するためのジェネレータメソッドを提供するクラスを作成します。
コードは以下の3つのファイルに分けて構成します:
Product.php— 在庫内のアイテムを表すProductクラスを作成します。コンストラクタのプロパティ昇格(constructor promotion)を使用して、public な$sku(string)、public な$name(string)、および public な$price(float) を定義してください。Inventory.php— 製品のコレクションを管理し、それらを反復処理するためのジェネレータメソッドを提供するInventoryクラスを作成します。Product ファイルをインクルードしてください。このクラスは以下の要件を満たす必要があります:- 製品を保存するための private な配列プロパティを持つこと
- 製品をコレクションに追加する
addProduct(Product $product)メソッドを持つこと - 各製品を1つずつ yield するジェネレータメソッド
allProducts(): Generatorを持つこと - 指定された最小価格以上の製品のみを yield するジェネレータメソッド
expensiveProducts(float $minPrice): Generatorを持つこと。このとき、キーを SKU、値を製品オブジェクトとするキー・値ペアを yield してください。
main.php— Inventory ファイルをインクルードします。1つの入力(最小価格のしきい値)を受け取ります。Inventoryインスタンスを作成し、次の3つの製品を追加してください:- SKU:
"A001", Name:"Laptop", Price:999.99 - SKU:
"A002", Name:"Mouse", Price:29.99 - SKU:
"A003", Name:"Monitor", Price:349.99
まず、
allProducts()を反復処理し、各製品の名前をそれぞれの行に出力します。次に、空行を1行出力し、続いて入力されたしきい値を使用して
expensiveProducts()を反復処理します。各高額製品について、"[sku]: [name] - $[price]"の形式でそれぞれの行に出力してください。価格は小数点以下ちょうど2桁でフォーマットしてください。- SKU:
このチャレンジでは、ジェネレータを使用してコレクションを遅延評価(lazy evaluation)で反復処理する方法を学びます。各製品は要求されたときにのみ yield されるため、このアプローチは大規模な在庫データに対してメモリ効率が良くなります。expensiveProducts() におけるキー・値の yield は、ジェネレータが構造化された反復データを提供できることを示しています。
自分で試してみよう
<?php
require_once 'Inventory.php';
// 入力を読み込む
$minPrice = floatval(trim(fgets(STDIN)));
// TODO: Inventory インスタンスを作成する
// TODO: 3つの商品を追加する:
// SKU: "A001", Name: "Laptop", Price: 999.99
// SKU: "A002", Name: "Mouse", Price: 29.99
// SKU: "A003", Name: "Monitor", Price: 349.99
// TODO: allProducts() をループして、各商品の名前を出力する
// TODO: 空行を出力する
// TODO: $minPrice を使用して expensiveProducts() をループする
// 各高額商品について、"[sku]: [name] - $[price]" を出力する
// 価格は小数点以下ちょうど2桁でフォーマットする
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
10オブジェクト指向の高度な概念
コンポジション vs 継承依存性の注入 (Dependency Injection)匿名クラスEnum (PHP 8.1)ファイバー (PHP 8.1)オブジェクトのクローン徹底解説ジェネレータとイテレータ