Функции в 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?