PHP: Функции

17 вопросов

1 Что такое замыкание (closure)? Что такое анонимная функция?

Анонимная функция - функция без имени, присваиваемая переменной. Замыкание - анонимная функция, захватывающая переменные из окружающей области видимости:

// Анонимная функция
$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.

Открыть отдельно →
2 Чем стрелочные функции (fn =>) отличаются от анонимных?
// Стрелочная (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;
};

Различия:

  • fn - только одно выражение (нет блока {})
  • Автоматический захват переменных по значению (без use)
  • Нельзя захватить по ссылке
  • Нельзя изменить захваченную переменную
Открыть отдельно →
3 Что такое захват переменных? (use)

Ключевое слово 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.

Открыть отдельно →
4 Как передать внешнюю переменную в замыкание?

Три способа:

$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));
Открыть отдельно →
5 Что такое генераторы? (yield, yield from)

Генератор - функция, которая возвращает значения по одному через 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);
}

Генератор - ленивый: вычисляет следующее значение только по запросу. Не загружает все данные в память.

Открыть отдельно →
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), итераторы - можно (если реализовано).

Открыть отдельно →
7 Как работает генератор? Что такое send() и return()?
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 после завершения генератора.

Открыть отдельно →
8 Что такое чистая функция?

Чистая функция - функция без побочных эффектов, результат зависит только от аргументов:

// Чистая - всегда одинаковый результат для одинаковых входных данных
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), распараллеливать, понимать.

Открыть отдельно →
9 Что такое функции высшего порядка?

Функция высшего порядка принимает функцию как аргумент или возвращает функцию:

// Принимает функцию
$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
Открыть отдельно →
10 Что такое каррирование?

Каррирование - преобразование функции с 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;
}
Открыть отдельно →
11 Что такое partial application?

Частичное применение - фиксация части аргументов функции:

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 - строго по одному.

Открыть отдельно →
12 Что такое pipe/compose?
// 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',
);
Открыть отдельно →
13 Что такое variadic функции?
// 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 параметр должен быть последним. Внутри функции это обычный массив.

Открыть отдельно →
14 Что такое named arguments? (PHP 8)
// Именованные аргументы - передача по имени параметра
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.

Открыть отдельно →
15 Что такое first-class callable syntax? (PHP 8.1)
// 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 проверяет существование метода, работает рефакторинг.

Открыть отдельно →
16 Что такое рекурсия? Когда использовать?

Рекурсия - вызов функцией самой себя:

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.

Открыть отдельно →
17 Что такое functional options pattern?

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');
Открыть отдельно →
🧠Квиз 🏆Лидеры 🎯Собесед. 📖Вопросы 📚База зн.