Генераторы и итераторы
Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 62 из 91.
Генераторы предоставляют простой способ создания итераторов без реализации полного интерфейса Iterator. Функция-генератор использует yield вместо return для последовательного создания значений, приостанавливая выполнение между каждым значением.
Вот базовый генератор, который создает последовательность чисел:
<?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Ключевым преимуществом является эффективность использования памяти. Вместо того чтобы создавать в памяти целый массив, генераторы создают значения по запросу. Это крайне важно при работе с большими наборами данных: генератор, обрабатывающий миллион записей, потребляет столько же памяти, сколько и генератор, обрабатывающий десять.
Генераторы также могут возвращать пары ключ-значение:
<?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. Однако в большинстве случаев генераторы предлагают более чистое решение с меньшим количеством шаблонного кода. Они особенно полезны в ООП, когда классу необходимо предоставить доступ к коллекции элементов без загрузки всего содержимого в память сразу.
Задание
ЛегкоДавайте создадим систему инвентаризации товаров, которая использует генераторы для эффективного перебора больших коллекций без одновременной загрузки всего объема данных в память. Вы создадите класс, который хранит товары и предоставляет методы-генераторы для итерации по ним различными способами.
Вы организуете свой код в трех файлах:
Product.php— Создайте классProduct, который представляет собой товар в инвентаре. Используйте продвижение свойств в конструкторе (constructor promotion) для определения публичного$sku(string), публичного$name(string) и публичного$price(float).Inventory.php— Создайте классInventory, который управляет коллекцией товаров и предоставляет методы-генераторы для итерации по ним. Подключите файл Product. Класс должен:- Иметь приватное свойство-массив для хранения товаров
- Иметь метод
addProduct(Product $product), который добавляет товар в коллекцию - Иметь метод-генератор
allProducts(): Generator, который возвращает (yield) каждый товар по одному за раз - Иметь метод-генератор
expensiveProducts(float $minPrice): Generator, который возвращает только те товары, цена которых больше или равна заданному минимуму, возвращая пары ключ-значение, где ключом является SKU, а значением — объект товара
main.php— Подключите файл Inventory. Вы получите один входной параметр: порог минимальной цены.Создайте экземпляр
Inventoryи добавьте эти три товара:- SKU:
"A001", Name:"Laptop", Price:999.99 - SKU:
"A002", Name:"Mouse", Price:29.99 - SKU:
"A003", Name:"Monitor", Price:349.99
Сначала выполните итерацию через
allProducts()и выведите название каждого товара на отдельной строке.Затем выведите пустую строку, после чего выполните итерацию через
expensiveProducts(), используя пороговое значение из входных данных. Для каждого дорогого товара выведите"[sku]: [name] - $[price]"на отдельной строке. Отформатируйте цену ровно с 2 знаками после запятой.- SKU:
Эта задача демонстрирует, как генераторы позволяют выполнять ленивую итерацию по коллекциям — каждый товар возвращается только по запросу, что делает этот подход эффективным с точки зрения использования памяти для больших инвентарных списков. Возврат пар ключ-значение в expensiveProducts() показывает, как генераторы могут предоставлять структурированные данные для итерации.
Шпаргалка
Генераторы предоставляют эффективный с точки зрения памяти способ создания итераторов с использованием yield вместо return. Они выдают значения по одному, приостанавливая выполнение между каждым значением.
Базовая функция-генератор:
<?php
function countTo(int $max): Generator {
for ($i = 1; $i <= $max; $i++) {
yield $i;
}
}
foreach (countTo(3) as $number) {
echo "$number\n";
}
Генераторы могут возвращать пары ключ-значение:
<?php
function userGenerator(): Generator {
yield 'admin' => 'Alice';
yield 'editor' => 'Bob';
}
foreach (userGenerator() as $role => $name) {
echo "$role: $name\n";
}
Основным преимуществом является эффективность использования памяти — генераторы создают значения по запросу, а не выстраивают целые массивы в памяти. Это особенно полезно при работе с большими наборами данных или коллекциями в контексте ООП.
Попробуйте сами
<?php
require_once 'Inventory.php';
// Чтение входных данных
$minPrice = floatval(trim(fgets(STDIN)));
// TODO: Создать экземпляр Inventory
// TODO: Добавить три продукта:
// 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: Перебрать продукты из expensiveProducts(), используя $minPrice
// Для каждого дорогого продукта вывести "[sku]: [name] - $[price]"
// Форматировать цену ровно с 2 знаками после запятой
?>В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово $thisМетодыСвойстваКонструктор (__construct)Деструктор (__destruct)Итоги — Простой калькулятор4Наследование
Основы наследованияКлючевое слово parent::Переопределение методовКлючевое слово finalАбстрактные классыИтоги — Иерархия сотрудников7Инкапсуляция
Public, Protected, PrivateПодробный разбор модификаторов доступаГеттеры и сеттерыСокрытие данныхConstructor Promotion (8.0)Итоги — Система учета студентов10Продвинутые концепции ООП
Композиция против наследованияВнедрение зависимостейАнонимные классыПеречисления (PHP 8.1)Файберы (PHP 8.1)Глубокое погружение в клонирование объектовГенераторы и итераторы13Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий2Пространства имен и автозагрузка
Введение в пространства именКлючевое слово useСтандарт автозагрузки PSR-4Автозагрузчик ComposerИтоги: структурированный проект5Интерфейсы и контракты
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовИнтерфейс против абстрактного классаType Hinting с интерфейсамиИтоги — Калькулятор фигур8Магические методы
Введение в магические методы__toString и __debugInfo__get, __set, __isset, __unset__call и __callStatic__clone и клонирование объектов__serialize и __unserializeИтоги — Кастомная коллекция11Система типов и обработка ошибок
Объявление типовNullable-типыТипы объединения и пересеченияКлассы исключенийИерархия пользовательских исключенийTry, Catch, FinallyИтоги — Валидатор форм14Проект: Управление библиотекой
Обзор проектаКлассы Book и User3Свойства классов
Свойства экземпляра и статические свойстваКонстанты в классахСтатические методы и свойстваПриватные и защищенные свойстваСвойства Readonly (PHP 8.1)Итоги — Менеджер банковского счета6Полиморфизм
Снова о переопределении методовПолиморфизм через интерфейсыType Hinting и Union TypesПозднее статическое связываниеИтоги — Payment Processor9Трейты
Введение в трейтыИспользование нескольких трейтовРазрешение конфликтов трейтовАбстрактные методы в трейтахТрейты и наследование12Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy