Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Генераторы

Обычная функция сначала собирает весь результат, а потом возвращает его целиком. Для миллиона строк это миллион элементов в памяти. Генератор отдаёт значения по одному — ровно тогда, когда их просят. Памяти нужно столько, сколько занимает один элемент.

yield

Функция со словом yield становится генератором. Каждый yield отдаёт одно значение и приостанавливает функцию до следующего запроса:

<?php
 
function numbers()
{
    yield 1;
    yield 2;
    yield 3;
}
 
foreach (numbers() as $number) {
    echo $number, ' ';
}

Выведется 1 2 3. Генератор перебирают циклом foreach, как массив.

Значения по требованию

Код генератора выполняется не сразу, а по шагам — между итерациями цикла:

<?php
 
function steps()
{
    echo '[начало] ';
    yield 'A';
    echo '[середина] ';
    yield 'B';
    echo '[конец]';
}
 
foreach (steps() as $step) {
    echo $step, ' ';
}

Выведется [начало] A [середина] B [конец]: функция продолжается с того места, где остановилась.

Генератор вместо массива

Диапазон чисел без создания массива:

<?php
 
function countTo(int $limit)
{
    for ($i = 1; $i <= $limit; $i++) {
        yield $i;
    }
}
 
$sum = 0;
 
foreach (countTo(1000000) as $number) {
    $sum += $number;
}
 
echo $sum; // 500000500000

Массив из миллиона чисел занял бы десятки мегабайт. Генератор хранит только текущее значение.

Ключи

yield может отдавать пару «ключ — значение»:

<?php
 
function prices()
{
    yield 'чай' => 250;
    yield 'кофе' => 400;
}
 
foreach (prices() as $name => $price) {
    echo $name, ': ', $price, "\n";
}

Остановка по условию

Генератор может быть бесконечным — перебор прекращает тот, кто его использует:

<?php
 
function evenNumbers()
{
    $number = 0;
 
    while (true) {
        $number += 2;
        yield $number;
    }
}
 
foreach (evenNumbers() as $number) {
    if ($number > 10) {
        break;
    }
    echo $number, ' ';
}

Выведется 2 4 6 8 10.

Чтение большого файла

Главное применение — обработка данных, которые не помещаются в память: большие файлы, выборки из базы. Генератор читает файл построчно:

<?php
 
function readLines(string $path)
{
    $handle = fopen($path, 'r');
 
    while (($line = fgets($handle)) !== false) {
        yield rtrim($line);
    }
 
    fclose($handle);
}
 
$file = sys_get_temp_dir() . '/lines-demo.txt';
file_put_contents($file, "первая\nвторая\nтретья\n");
 
foreach (readLines($file) as $number => $line) {
    echo $number + 1, '. ', $line, "\n";
}

Частая ошибка

Генератор проходят только один раз. После завершения цикла начать его заново нельзя — нужно вызвать функцию ещё раз. И к нему не применимы count() и обращение по индексу:

<?php
 
function letters()
{
    yield 'a';
    yield 'b';
}
 
$generator = letters();
 
echo implode(', ', iterator_to_array($generator)), "\n"; // a, b
echo count(iterator_to_array(letters()));                 // 2

iterator_to_array() превращает генератор в массив — но тогда теряется экономия памяти.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.