17 вопросов
Анонимная функция - функция без имени, присваиваемая переменной. Замыкание - анонимная функция, захватывающая переменные из окружающей области видимости:
// Анонимная функция
$greet = function(string $name): string {
return "Hello, $name";
};
echo $greet('PHP'); // "Hello, PHP"
// Замыкание - захват через use
$prefix = 'Hello';
$greet = function(string $name) use ($prefix): string {
return "$prefix, $name";
};
// По ссылке
$counter = 0;
$inc = function() use (&$counter) { $counter++; };
$inc();
echo $counter; // 1Все анонимные функции в PHP - экземпляры класса Closure.
// Стрелочная (PHP 7.4+) - одно выражение, авто-захват
$double = fn(int $x): int => $x * 2;
$factor = 3;
$multiply = fn($x) => $x * $factor; // $factor захвачен автоматически
// Эквивалент анонимной функции
$multiply = function($x) use ($factor) {
return $x * $factor;
};Различия:
Ключевое слово use позволяет замыканию захватить переменные из внешней области видимости:
$message = 'Hello';
$logger = function(string $level) use ($message) {
echo "[$level] $message";
};
// По значению - замыкание получает копию
$x = 10;
$fn = function() use ($x) { echo $x; };
$x = 20;
$fn(); // 10 (копия на момент создания)
// По ссылке
$x = 10;
$fn = function() use (&$x) { echo $x; };
$x = 20;
$fn(); // 20 (актуальное значение)В PHP (в отличие от Go/JS) переменные НЕ захватываются автоматически - нужно явно указать через use.
Три способа:
$db = new PDO('...');
// 1. use - захват из внешней области видимости
$query = function(string $sql) use ($db) {
return $db->query($sql);
};
// 2. Аргумент функции
$query = function(PDO $db, string $sql) {
return $db->query($sql);
};
// 3. Стрелочная функция (авто-захват)
$query = fn(string $sql) => $db->query($sql);
// 4. Closure::bind - привязка к объекту
$fn = Closure::bind(function() {
return $this->privateField;
}, $obj, get_class($obj));Генератор - функция, которая возвращает значения по одному через yield:
function fibonacci(): Generator {
$a = 0; $b = 1;
while (true) {
yield $a;
[$a, $b] = [$b, $a + $b];
}
}
foreach (fibonacci() as $num) {
if ($num > 100) break;
echo "$num "; // 0 1 1 2 3 5 8 13 21 34 55 89
}
// yield from - делегирование
function merged(): Generator {
yield from [1, 2, 3];
yield from range(4, 6);
}Генератор - ленивый: вычисляет следующее значение только по запросу. Не загружает все данные в память.
Генератор - упрощенный итератор, создаваемый функцией с yield. Не нужно реализовывать 5 методов Iterator.
// Итератор - много boilerplate
class RangeIterator implements Iterator {
private int $current;
public function __construct(private int $start, private int $end) {
$this->current = $start;
}
public function current(): int { return $this->current; }
public function key(): int { return $this->current - $this->start; }
public function next(): void { $this->current++; }
public function rewind(): void { $this->current = $this->start; }
public function valid(): bool { return $this->current <= $this->end; }
}
// Генератор - 3 строки
function range_gen(int $start, int $end): Generator {
for ($i = $start; $i <= $end; $i++) { yield $i; }
}Генераторы нельзя перемотать назад (rewind), итераторы - можно (если реализовано).
function accumulator(): Generator {
$sum = 0;
while (true) {
$value = yield $sum; // yield возвращает и принимает значения
if ($value === null) break;
$sum += $value;
}
return $sum; // финальное значение
}
$gen = accumulator();
$gen->current(); // 0 (первый yield)
$gen->send(10); // отправляет 10, получает 10
$gen->send(20); // отправляет 20, получает 30
$gen->send(null); // завершает цикл
echo $gen->getReturn(); // 30 (значение из return)send() передает значение в генератор (становится результатом yield). getReturn() получает значение из return после завершения генератора.
Чистая функция - функция без побочных эффектов, результат зависит только от аргументов:
// Чистая - всегда одинаковый результат для одинаковых входных данных
function add(int $a, int $b): int {
return $a + $b;
}
// Нечистая - зависит от внешнего состояния
function getUser(int $id): User {
return DB::find($id); // побочный эффект: обращение к БД
}
// Нечистая - модифицирует внешнее состояние
function logMessage(string $msg): void {
file_put_contents('log.txt', $msg); // побочный эффект
}Преимущества: легко тестировать, кешировать (memoize), распараллеливать, понимать.
Функция высшего порядка принимает функцию как аргумент или возвращает функцию:
// Принимает функцию
$doubled = array_map(fn($x) => $x * 2, [1, 2, 3]); // [2, 4, 6]
$evens = array_filter([1, 2, 3, 4], fn($x) => $x % 2 === 0); // [2, 4]
$sum = array_reduce([1, 2, 3], fn($carry, $x) => $carry + $x, 0); // 6
usort($arr, fn($a, $b) => $a <=> $b);
// Возвращает функцию
function multiplier(int $factor): Closure {
return fn(int $x): int => $x * $factor;
}
$triple = multiplier(3);
echo $triple(5); // 15Каррирование - преобразование функции с N аргументами в цепочку функций с одним аргументом:
// Обычная функция
function add(int $a, int $b): int { return $a + $b; }
// Каррированная версия
function curriedAdd(int $a): Closure {
return fn(int $b): int => $a + $b;
}
$add5 = curriedAdd(5);
echo $add5(3); // 8
echo $add5(10); // 15
// Универсальное каррирование
function curry(callable $fn): Closure {
$arity = (new ReflectionFunction($fn))->getNumberOfRequiredParameters();
$args = [];
$inner = function() use ($fn, $arity, &$args, &$inner) {
$args = array_merge($args, func_get_args());
return count($args) >= $arity ? $fn(...$args) : $inner;
};
return $inner;
}Частичное применение - фиксация части аргументов функции:
function logger(string $level, string $context, string $msg): void {
echo "[$level][$context] $msg\n";
}
// Частичное применение через замыкание
$errorLogger = fn(string $msg) => logger('ERROR', 'app', $msg);
$errorLogger('Something failed');
// Или более гибко
function partial(callable $fn, ...$partial): Closure {
return fn(...$args) => $fn(...$partial, ...$args);
}
$dbLogger = partial('logger', 'INFO', 'database');
$dbLogger('Query executed');Разница с каррированием: partial application фиксирует любое количество аргументов за раз, currying - строго по одному.
// pipe - применяет функции слева направо
function pipe(mixed $value, callable ...$fns): mixed {
foreach ($fns as $fn) {
$value = $fn($value);
}
return $value;
}
$result = pipe(
' Hello World ',
'trim',
'strtolower',
fn($s) => str_replace(' ', '-', $s),
);
// 'hello-world'
// compose - справа налево (математическая композиция)
function compose(callable ...$fns): Closure {
return function(mixed $value) use ($fns) {
foreach (array_reverse($fns) as $fn) {
$value = $fn($value);
}
return $value;
};
}
$slugify = compose(
fn($s) => str_replace(' ', '-', $s),
'strtolower',
'trim',
);// Variadic параметр - принимает любое количество аргументов
function sum(int ...$numbers): int {
return array_sum($numbers);
}
echo sum(1, 2, 3, 4, 5); // 15
// Spread operator для передачи
$args = [1, 2, 3];
echo sum(...$args); // 6
// Variadic после обычных параметров
function log(string $level, string ...$messages): void {
foreach ($messages as $msg) {
echo "[$level] $msg\n";
}
}
log('ERROR', 'File not found', 'Permission denied');Variadic параметр должен быть последним. Внутри функции это обычный массив.
// Именованные аргументы - передача по имени параметра
function createUser(string $name, int $age, string $role = 'user'): void { /* ... */ }
// Можно пропускать параметры с дефолтами
createUser(name: 'John', age: 30);
createUser(age: 25, name: 'Jane'); // порядок не важен
createUser('John', age: 30, role: 'admin');
// Распаковка именованных аргументов
$args = ['name' => 'John', 'age' => 30];
createUser(...$args);Именованные аргументы улучшают читаемость, особенно для функций с множеством параметров или булевыми флагами. Но делают рефакторинг (переименование параметров) breaking change.
// PHP 8.1: получение ссылки на функцию через ...
$fn = strlen(...); // вместо 'strlen'
$fn = $obj->method(...); // вместо [$obj, 'method']
$fn = User::create(...); // статический метод
// Использование
$lengths = array_map(strlen(...), ['hello', 'world']); // [5, 5]
// Удобно для pipeline
$result = array_map(
strtolower(...),
array_filter($items, is_string(...))
);Синтаксис function(...) создает объект Closure. Это безопаснее строк: IDE проверяет существование метода, работает рефакторинг.
Рекурсия - вызов функцией самой себя:
function factorial(int $n): int {
if ($n <= 1) return 1; // базовый случай
return $n * factorial($n - 1); // рекурсивный вызов
}
// Обход дерева - классический случай для рекурсии
function flatten(array $arr): array {
$result = [];
foreach ($arr as $item) {
if (is_array($item)) {
$result = array_merge($result, flatten($item));
} else {
$result[] = $item;
}
}
return $result;
}Используйте для: обхода деревьев, фракталов, divide-and-conquer. Избегайте для линейных задач (используйте циклы). PHP не оптимизирует хвостовую рекурсию, глубокая рекурсия может вызвать stack overflow.
Functional options - паттерн из Go, в PHP аналогичная задача решается через Builder или именованные аргументы:
// PHP: Builder pattern (аналог functional options)
class QueryBuilder {
private int $limit = 10;
private int $offset = 0;
private string $orderBy = 'id';
public function limit(int $n): self {
$clone = clone $this;
$clone->limit = $n;
return $clone;
}
public function offset(int $n): self {
$clone = clone $this;
$clone->offset = $n;
return $clone;
}
}
$query = (new QueryBuilder())->limit(20)->offset(40);
// Или PHP 8 named arguments
$client = new HttpClient(timeout: 30, retries: 3, baseUrl: 'https://api.com');