Частичное применение функций появится в PHP 8.6
Частичное применение функций (Partial Function Application, PFA) — одно из ключевых нововведений PHP 8.6. Оно напрямую решает проблему шаблонного кода при работе с обратными вызовами (callback), предлагая синтаксис на основе заполнителей для создания специализированных версий функций.
Это улучшение пригодится разработчикам, работающим с функциональным программированием в PHP, и позволит упростить обработку данных, сократить количество boilerplate-кода и улучшить читаемость скриптов.
Что такое частичное применение функции
Частичное применение функций в PHP 8.6 позволит писать частично применённую функцию (замыкание), вызывая функцию с несколькими аргументами и используя заполнители для остальных. Вместо выполнения PHP возвращает замыкание, список параметров которого автоматически формируется из отсутствующих частей.
Заполнители:
?для «только одного аргумента здесь»...для «всех оставшихся аргументов»
Пример с str_contains:
// Допустим, мы хотим создать функцию, которая проверяет, содержит ли строка подстроку "error".
// Без PFA:
$hasError = fn($s) => str_contains($s, 'error');
// С PFA, фиксируя искомую подстроку (иглу):
$hasError = str_contains(?, 'error');
var_dump($hasError('An error occurred')); // true
var_dump($hasError('All good')); // falseЧто изменилось: Исчезает необходимость явно объявлять анонимную функцию для простой фиксации аргумента. Синтаксис становится декларативным: мы прямо указываем, какой аргумент фиксируется ('error'), а какой остаётся свободным (?).
Это актуально при работе с массивами, фильтрацией данных и обработкой событий, где часто требуются однотипные callback-функции.
Фиксация одного аргумента:
function add4(int $a, int $b, int $c, int $d): int
{
return $a + $b + $c + $d;
}
// Задача: создать функцию, которая прибавляет 1, 3 и 4 к переданному числу.
$addFixed = add4(1, ?, 3, 4);
// Эквивалентная стрелочная функция:
// $addFixed = static fn(int $b): int => add4(1, $b, 3, 4);
echo $addFixed(2); // 1 + 2 + 3 + 4 = 10Что изменилось: Вместо объявления стрелочной функции, которая явно передаёт аргументы, мы используем вызов с заполнителем. Создаваемая функция $addFixed имеет арность 1, хотя исходная add4 принимает 4 аргумента.
Таким образом, PFA убирает рутину: вместо явного объявления анонимной функции для подстановки одного аргумента вы сразу указываете, что фиксируете. Это не просто «продолжение концепции», а прямой инструмент для уменьшения кода в типовых сценариях работы с array_map, array_filter и другими функциями высшего порядка
Также можете оставить несколько незаполненных аргументов.
Фиксация нескольких аргументов:
// Задача: создать функцию, которая складывает 1 и 3 с двумя переданными числами.
$addOneAndThree = add4(1, ?, 3, ?);
// Эквивалентная стрелочная функция:
// $addOneAndThree = static fn(int $b, int $d): int => add4(1, $b, 3, $d);
echo $addOneAndThree(5, 7); // 1 + 5 + 3 + 7 = 16Что изменилось: Можно использовать несколько заполнителей ?, создавая функцию, которая ожидает несколько оставшихся аргументов, без необходимости явно перечислять их в сигнатуре стрелочной функции.
И «Все остальное» с ...
Использование заполнителя ...:
// Задача: создать функцию, которая прибавляет 1 к трём переданным числам.
$addOneToThree = add4(1, ...);
// Эквивалентная стрелочная функция:
// $addOneToThree = static fn(int $b, int $c, int $d): int => add4(1, $b, $c, $d);
echo $addOneToThree(2, 3, 4); // 1 + 2 + 3 + 4 = 10Что изменилось: Заполнитель ... захватывает все оставшиеся аргументы, что позволяет лаконично создавать адаптеры или откладывать выполнение функции без явного описания всех параметров вручную.
PFA позволяет записывать обратные вызовы в более компактной и декларативной форме. Больше не нужно использовать шаблонные стрелочные функции только для перегруппировки или исправления аргументов. Используйте заполнители ? и ... для указания свободных аргументов, а PHP сделает все остальное.
PFA полностью поддерживает современные возможности PHP, такие как именованные и вариативные аргументы, что позволяет гибко фиксировать параметры независимо от их позиции.
PFA и современный синтаксис PHP
Именованные аргументы и порядок:
function stuff(int $i, string $s, float $f, Point $p, int $m = 0): string { /* ... */ }
// Именованные значения, расположенные не по порядку, все равно работают:
$c = stuff(?, ?, f: 3.5, p: $point);
// Замыкание ожидает (int $i, string $s)
// Именованные заполнители определяют собственный порядок параметров:
$c = stuff(s: ?, i: ?, p: ?, f: 3.5);
// Замыкание ожидает (string $s, int $i, Point $p)Что изменилось: Исчезает сложность ручного переупорядочивания аргументов в анонимной функции. PFA позволяет комбинировать именованные аргументы и заполнители, а именованные заполнители дают полный контроль над порядком параметров в итоговом замыкании.
Такая гибкость важна при использовании PHP в современных фреймворках, таких как Laravel или Symfony, где часто применяются цепочки вызовов и middleware.
Вариативные функции:
function things(int $i, ?float $f = null, Point ...$points) { /* ... */ }
// Сохраняйте вариативность открытой:
$c = things(1, 3.14, ...);
// Замыкание ожидает (Point ...$points)
// Принудительное точное подсчитывание (вариативные становятся обязательными слотами):
// Указание трёх '?' после первых двух захватывает первые три элемента из вариативного параметра.
$c = things(?, ?, ?, ?);
// Замыкание ожидает (int $i, ?float $f, Point $points0, Point $points1)Что изменилось: Упрощается работа с вариативными функциями. Заполнитель ... сохраняет вариативность, а использование ? для каждого слота позволяет превратить вариативный параметр в серию явных, что даёт контроль над их количеством без написания сложных обёрток.
PFA не только заменяет шаблонный код, но и открывает новые паттерны, например, создание отложенных вычислений. Однако у подхода есть и важное ограничение.
Особые случаи и ограничения
Вы также можете легко реализовать Thunk-функции с помощью PFA.
Создание Thunk-функций:
function expensive(int $a, int $b, Point $c) { /* heavy work */ }
// Предварительно заполнить всё, отложить выполнение:
$thunk = expensive(3, 4, $pt, ...); // Замыкание с отсутствием требуемых параметров
// Позже:
$result = $thunk();Что изменилось: Появляется лаконичный способ создания thunk'ов (функций отложенного вычисления) без необходимости писать обёртки вида fn() => expensive(3, 4, $pt).
Вы не можете частично применять конструкторы (new). Вместо этого можно использовать статические методы или фабричные функции.
$maker = Widget::make(?, size: 10); // OK
$new = new Widget(?, 10); // Ошибка компиляцииЧто изменилось: Синтаксис PFA не применяется к конструкторам через оператор new. Для создания объектов с частично применёнными параметрами следует использовать статические фабричные методы.
PFA на практике: замена шаблонных стрелочных функций
Главная выгода PFA — элиминация шаблонного кода. Нагляднее всего это проявляется при прямой замене однотипных стрелочных функций. Давайте сравним подходы на реальных задачах.
Замена подстроки в массиве:
// Старый способ (стрелочная функция)
$newArray = array_map(static fn($s) => str_replace('hello', 'hi', $s), $strings);
// Новый способ с PFA (короче и нагляднее)
$newArray = array_map(str_replace('hello', 'hi', ?), $strings);Что изменилось: Исчезает шаблонная стрелочная функция static fn($s) => .... Синтаксис акцентирует операцию (str_replace) и данные (?), а не структуру обёртки.
Это упрощает рефакторинг, тестирование и поддержку кода, особенно в больших проектах с активной разработкой.
Фильтрация по значению:
$allowed = [1, 2, 3];
// Старый способ
$filtered = array_filter($input, static fn($v) => in_array($v, $allowed, true));
// Новый способ с PFA
$filtered = array_filter($input, in_array(?, $allowed, strict: true));Что изменилось: Синтаксис становится более декларативным, приближаясь по чтению к инструкции «отфильтровать по вхождению в массив $allowed».
Создание "фабрики" действий:
function logMessage(string $level, string $message, array $context): void { /* ... */ }
// Старый способ: создаём новую функцию
$logError = static fn(string $msg, array $ctx = []) => logMessage('error', $msg, $ctx);
// Новый способ с PFA
$logError = logMessage('error', ?, ?);Что изменилось: PFA позволяет создавать специализированные функции ($logError) прямо на месте вызова, без явного объявления новой анонимной функции.
Он также удобен для операторов pipe.
Применение в операторах pipe (|>):
$foo
|> array_map(strtoupper(...), ?)
|> array_filter(?, is_numeric(...));
// Правая сторона pipe требует callable максимум с одним аргументом; PFA предоставляет его в лаконичной форме.Что изменилось: PFA позволяет создавать одноаргументные callable прямо внутри конвейера, делая запись цепочки преобразований значительно короче и читаемее по сравнению с явным объявлением промежуточных стрелочных функций.
Практический пример
Этот подход особенно эффективен, когда мы работаем с конвейерами данных или цепочками вызовов. Проиллюстрируем это на примере из области обработки HTTP-запросов, когда необходимо добавить заголовки в HTTP-запрос. Можно заранее заполнить имя и значение заголовка, оставив массив запроса для заполнения позже.
Практический пример с HTTP-запросом:
// 1. Базовые функции-обработчики запроса
function addHeader(array $req, string $name, string $value): array
{
$req['headers'][$name] = $value;
return $req;
}
function setMethod(array $req, string $method): array
{
$req['method'] = $method;
return $req;
}
// 2. Создаём специализированные обработчики с помощью PFA
$withAuth = addHeader(?, 'Authorization', 'Bearer TOKEN');
$asPost = setMethod(?, 'POST');
// 3. Собираем конвейер обработки запроса
$request = ['url' => '/api/data', 'headers' => []];
$request = $withAuth($request); // Добавляем заголовок
$request = $asPost($request); // Меняем метод
// Или даже так (с учётом поддержки pipeline):
// $request = $request |> $withAuth(?) |> $asPost(?);Что изменилось: Мы создаём переиспользуемые компоненты ($withAuth, $asPost) с помощью PFA прямо на месте, избегая объявления отдельных функций-хелперов или анонимных функций для фиксации параметров. Это повышает модульность и ясность кода при построении конвейеров обработки.
Советы по внедрению и границы применения
Прежде чем массово внедрять PFA в проект, стоит учесть несколько практических аспектов:
- Обратная совместимость: PFA доступно только с PHP 8.6. Для кода, который должен работать на старых версиях, можно использовать полифиллы — пользовательские функции, имитирующие поведение заполнителей
?и...черезClosure::fromCallableиfunc_get_args(). - Читаемость для команды: PFA делает код декларативнее, но может быть непривычным для разработчиков, незнакомых с функциональным стилем. Вводите его постепенно, начиная с самых простых случаев замены
fn(...) => .... Избегайте создания чрезмерно сложных цепочек с множеством заполнителей, которые трудно мысленно сопоставить с оригинальной сигнатурой функции. - Производительность: PFA создаёт замыкание под капотом, аналогично стрелочной функции. Накладные расходы пренебрежимо малы для подавляющего большинства сценариев. Однако в самом горячем, микрооптимизированном коде (например, внутри циклов на сотни тысяч итераций) стоит замерять разницу, хотя она вряд ли будет значительной.
- Статический анализ: Использование заполнителей может сделать некоторые ошибки типизации менее очевидными. Инструменты статического анализа, такие как PHPStan или Psalm, с поддержкой PHP 8.6 будут корректно выводить типы для частично применённых функций, что поможет сохранить надёжность кода.
Для команд, применяющих практики CI/CD и автоматическое тестирование, это означает более стабильный и предсказуемый код, меньшее количество runtime-ошибок и упрощённую отладку.
Заключение
Частичное применение функций является дополнением, позволяющим сократить шаблонный код и повысить его декларативность при работе с обратными вызовами. Позволяя фиксировать часть аргументов функции с помощью заполнителей, PFA упрощает создание лаконичных, понятных callable без необходимости использования многословных стрелочных функций. С PFA код, связанный с обратными вызовами и высшими функциями, становится не только короче, но и семантически яснее. Вместо явного описания передачи аргумента через анонимную функцию используется прямое указание фиксируемых значений. Такой подход делает логику кода более явной и снижает когнитивную нагрузку при чтении кода.
PFA — это элегантный инструмент для уменьшения шума в коде. Начинайте с малого: в следующий раз, когда будете писать fn($x) => str_contains($x, 'pattern'), попробуйте заменить это на str_contains(?, 'pattern'). Со временем это сделает ваш код не только компактнее, но и выразительнее. Такое внедрение соответствует современным стандартам веб-разработки и способствует созданию чистого, масштабируемого кода.
Хотите глубже разобраться в основах? Изучите Руководство по замыканиям и стрелочным функциям в PHP для полного понимания концепций, на которых построено PFA.
Подробнее о частичном применении функций читайте в официальном RFC.