Функциональное программирование в PHP: от теории к практике
PHP традиционно ассоциируется с императивным и объектно-ориентированным подходами, однако современный язык предоставляет мощные инструменты для функционального программирования (ФП). В этой статье мы разберём, как применять принципы ФП в PHP: от анонимных функций до каррирования и иммутабельности. Вы узнаете, как сделать код более предсказуемым, тестируемым и лаконичным.
1. Основы: анонимные функции и замыкания
Анонимные функции (лямбды) — краеугольный камень ФП в PHP. Они позволяют передавать поведение как данные.
<?php
// Простая анонимная функция
$greet = function($name) {
return "Привет, $name!";
};
echo $greet('Мир'); // Привет, Мир!
// Использование с array_map
$numbers = [1, 2, 3, 4, 5];
$squared = array_map(function($n) {
return $n * $n;
}, $numbers);
print_r($squared); // [1, 4, 9, 16, 25]
?>
Замыкания (closures) — это анонимные функции, которые захватывают переменные из внешней области видимости с помощью ключевого слова use:
<?php
$factor = 3;
$multiply = function($n) use ($factor) {
return $n * $factor;
};
echo $multiply(5); // 15
// Замыкание с изменяемой переменной (по ссылке)
$counter = 0;
$increment = function() use (&$counter) {
$counter++;
};
$increment();
echo $counter; // 1
?>
Замыкания незаменимы при создании callback-функций и фабрик.
2. Функции высшего порядка: array_map, array_filter, array_reduce
В PHP встроены классические функции высшего порядка, которые принимают другие функции в качестве аргументов:
array_map— преобразует каждый элемент массива;array_filter— отфильтровывает элементы по условию;array_reduce— свёртка массива в одно значение.
<?php
$data = [1, 2, 3, 4, 5, 6];
// Фильтрация чётных чисел
$even = array_filter($data, function($n) {
return $n % 2 === 0;
}); // [2, 4, 6]
// Удвоение каждого элемента
$doubled = array_map(function($n) {
return $n * 2;
}, $even); // [4, 8, 12]
// Сумма всех значений
$sum = array_reduce($doubled, function($carry, $n) {
return $carry + $n;
}, 0); // 24
?>
Обратите внимание: array_filter сохраняет ключи. Если нужна переиндексация, используйте array_values().
3. Иммутабельность и работа с данными
Функциональное программирование поощряет иммутабельность — неизменность данных. Вместо модификации существующих объектов создаются новые.
<?php
// Императивный подход (мутация)
$user = ['name' => 'Иван', 'age' => 25];
$user['age'] = 26; // меняем исходный массив
// Функциональный подход (иммутабельно)
$user = ['name' => 'Иван', 'age' => 25];
$updatedUser = array_merge($user, ['age' => 26]); // новый массив
?>
Для работы с массивами используйте array_merge, array_slice и spread-оператор (PHP 7.4+):
<?php
$original = [1, 2, 3];
$new = [...$original, 4]; // [1, 2, 3, 4] — оригинал не изменился
?>
Иммутабельность упрощает отладку и делает код потокобезопасным.
4. Каррирование и частичное применение
Каррирование — преобразование функции от нескольких аргументов в последовательность функций от одного аргумента. Частичное применение — фиксация части аргументов.
В PHP нет встроенного каррирования, но его легко реализовать:
<?php
// Каррированная функция сложения
function curryAdd($a) {
return function($b) use ($a) {
return function($c) use ($a, $b) {
return $a + $b + $c;
};
};
}
$add5 = curryAdd(5);
$add5And3 = $add5(3);
echo $add5And3(2); // 10 (5+3+2)
// Частичное применение с помощью Closure::fromCallable
function multiply($x, $y) {
return $x * $y;
}
$double = function($y) {
return multiply(2, $y);
};
echo $double(5); // 10
?>
Библиотека functional-php предоставляет готовые утилиты для каррирования:
<?php
// Требуется composer require lstrojny/functional-php
$curried = Functional\curry('array_map');
$square = $curried(function($n) { return $n * $n; });
$result = $square([1, 2, 3]); // [1, 4, 9]
?>
5. Практический пример: конвейерная обработка данных
Соберём всё вместе — создадим конвейер (pipeline) для обработки массива данных:
<?php
$data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Определяем шаги конвейера как отдельные функции
$filterEven = function($arr) {
return array_filter($arr, fn($n) => $n % 2 === 0);
};
$square = function($arr) {
return array_map(fn($n) => $n * $n, $arr);
};
$toJson = function($arr) {
return json_encode(array_values($arr));
};
// Композиция функций (pipeline)
$pipeline = function($data) use ($filterEven, $square, $toJson) {
return $toJson($square($filterEven($data)));
};
echo $pipeline($data); // "[4,16,36,64,100]"
?>
Такой подход делает код декларативным: каждый шаг изолирован