Урок по треку PHP 7 мин

Функции, замыкания и генераторы

use и стрелочные функции, callable, именованные аргументы, yield и yield from.

Функции в PHP это и обычные объявления, и объекты-замыкания, и генераторы. На собесе проверяют захват переменных, разницу function и fn, и умение обработать большие данные генератором.

Параметры

function greet(string $name = 'гость', string $mark = '!'): string
{
    return "Привет, $name$mark";
}

greet(mark: '?');                 // именованные аргументы (PHP 8.0)
function sum(int ...$nums): int { return array_sum($nums); }
sum(...[1, 2, 3]);                // распаковка

Массивы и строки передаются по значению с copy-on-write, объекты как дескриптор. Ссылка &$x нужна, только если функция меняет переменную вызывающего (sort, preg_match с $matches).

Статическая переменная static $n = 0; живёт между вызовами функции в рамках запроса.

Анонимные функции и use

$rate = 1.2;
$withTax = function (float $p) use ($rate): float { return $p * $rate; };
$rate = 2.0;
$withTax(100); // 120: значение захвачено при создании
  • use ($x) захватывает значение в момент создания.
  • use (&$x) захватывает по ссылке: видит изменения и может менять.
  • Внутри метода замыкание автоматически привязывает $this. static function этого не делает и не держит объект.

Стрелочные функции

$names = array_map(fn(User $u) => $u->name, $users);

fn (PHP 7.4) содержит одно выражение и сам захватывает используемые переменные по значению. Изменить внешнюю переменную нельзя.

callable

Строка 'strlen', массив [$obj, 'method'], 'Class::method', Closure, объект с __invoke и first-class callable strlen(...) (PHP 8.1). Последний вариант предпочтителен: его проверяет статический анализ.

Генераторы

Функция с yield возвращает Generator, а её тело выполняется по мере перебора:

function readLines(string $path): Generator
{
    $h = fopen($path, 'r');
    try {
        while (($line = fgets($h)) !== false) {
            yield rtrim($line, "\r\n");
        }
    } finally {
        fclose($h);
    }
}

foreach (readLines('huge.log') as $line) { /* память постоянна */ }

Возможности:

  • yield $key => $value отдаёт пары;
  • yield from $other делегирует другому генератору или массиву (ключи не перенумеровываются!);
  • return $x и $gen->getReturn() для итога;
  • $gen->send($v) передаёт значение внутрь.

Ограничения: генератор одноразовый, нет count и доступа по индексу. iterator_to_array($gen, false) собирает значения без ключей.

Конвейеры

Генераторы складываются в ленивые цепочки, где данные идут по одному элементу:

function filterErrors(iterable $lines): Generator {
    foreach ($lines as $n => $l) if (str_contains($l, 'ERROR')) yield $n => $l;
}

Так же работают курсоры ORM: cursor() и lazy() в Laravel, toIterable() в Doctrine.

Частые ошибки

  • Ожидать, что use ($x) увидит последующие изменения $x.
  • Пытаться изменить внешнюю переменную из fn.
  • Перебирать генератор второй раз.
  • iterator_to_array с ключами после yield from: значения с одинаковыми ключами перезапишутся.

Что спросят на собесе

  • Чем function () use () отличается от fn?
  • Как захватить переменную по ссылке и зачем это может понадобиться?
  • Что такое генератор и где вы его применяли?
  • Что делает yield from?
  • Какие бывают callable?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.