Menu
Coddy logo textTech

Генераторы и итераторы

Часть раздела Объектно-ориентированное программирование путешествия по 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. Однако в большинстве случаев генераторы предлагают более чистое решение с меньшим количеством шаблонного кода. Они особенно полезны в ООП, когда классу необходимо предоставить доступ к коллекции элементов без загрузки всего содержимого в память сразу.

challenge icon

Задание

Легко

Давайте создадим систему инвентаризации товаров, которая использует генераторы для эффективного перебора больших коллекций без одновременной загрузки всего объема данных в память. Вы создадите класс, который хранит товары и предоставляет методы-генераторы для итерации по ним различными способами.

Вы организуете свой код в трех файлах:

  • 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 знаками после запятой.

Эта задача демонстрирует, как генераторы позволяют выполнять ленивую итерацию по коллекциям — каждый товар возвращается только по запросу, что делает этот подход эффективным с точки зрения использования памяти для больших инвентарных списков. Возврат пар ключ-значение в 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 знаками после запятой

?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование