Функциональное программирование в PHP: продвинутые техники и примеры

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Функциональное программирование в PHP: продвинутый уровень

PHP традиционно ассоциируется с императивным и объектно-ориентированным стилями, однако современный язык (особенно с версии 7.4 и выше) предоставляет мощные инструменты для применения функциональной парадигмы. В этой статье мы разберём продвинутые концепции функционального программирования (ФП) в PHP, которые помогут писать более предсказуемый, тестируемый и выразительный код.

1. Чистые функции и неизменяемость данных

В основе ФП лежат чистые функции — функции, которые:

  • Зависят только от своих аргументов (не используют глобальные переменные или состояние вне области видимости);
  • Не вызывают побочных эффектов (не изменяют внешние данные, не пишут в файлы, не отправляют HTTP-запросы);
  • Для одних и тех же входных данных всегда возвращают один и тот же результат.

В PHP для обеспечения чистоты функций активно применяется неизменяемость данных. Вместо мутации существующих массивов или объектов создаются их копии с изменениями.

<?php
// Нечистая функция (использует глобальный массив)
$globalData = [1, 2, 3];
function addElementImpure($value) {
    global $globalData;
    $globalData[] = $value; // Побочный эффект
    return $globalData;
}

// Чистая функция (возвращает новый массив)
function addElementPure(array $data, $value): array {
    $newData = $data;
    $newData[] = $value;
    return $newData;
}

$original = [1, 2, 3];
$modified = addElementPure($original, 4);
// $original остаётся [1, 2, 3], $modified = [1, 2, 3, 4]
?>

2. Замыкания (Closures) и захват переменных

Замыкания в PHP — это анонимные функции, которые могут захватывать переменные из родительской области видимости через ключевое слово use. Это основа для создания функций высшего порядка.

Важно различать захват по значению (по умолчанию) и по ссылке (&use). В контексте ФП предпочтительнее захват по значению, чтобы не нарушать чистоту.

<?php
$multiplier = 3;

// Замыкание, захватывающее $multiplier по значению
$multiply = function ($value) use ($multiplier) {
    return $value * $multiplier;
};

echo $multiply(5); // 15

// Пример функции высшего порядка, возвращающей замыкание
function createGreeter(string $greeting): Closure {
    return function (string $name) use ($greeting) {
        return "{$greeting}, {$name}!";
    };
}

$sayHello = createGreeter('Hello');
echo $sayHello('World'); // Hello, World!
?>

3. Каррирование (Currying) и частичное применение

Каррирование — это преобразование функции, принимающей несколько аргументов, в последовательность функций, каждая из которых принимает один аргумент. Частичное применение — фиксация части аргументов для получения новой функции с меньшим числом параметров.

В PHP нет встроенной поддержки каррирования, но его можно реализовать с помощью замыканий.

<?php
// Обычная функция
function add(int $a, int $b, int $c): int {
    return $a + $b + $c;
}

// Каррированная версия
$curriedAdd = function (int $a) {
    return function (int $b) use ($a) {
        return function (int $c) use ($a, $b) {
            return $a + $b + $c;
        };
    };
};

echo $curriedAdd(1)(2)(3); // 6

// Частичное применение с помощью вспомогательной функции
function partial(callable $fn, ...$fixedArgs): Closure {
    return function (...$remainingArgs) use ($fn, $fixedArgs) {
        return $fn(...array_merge($fixedArgs, $remainingArgs));
    };
}

$addFive = partial('add', 2, 3);
echo $addFive(10); // 15 (2 + 3 + 10)
?>

4. Композиция функций и конвейеры

Композиция функций позволяет объединить несколько простых функций в одну сложную, передавая результат одной функции на вход следующей. Это ключевой паттерн для построения читаемых конвейеров обработки данных.

В PHP 8.1 появился атрибут #[Pure], который помогает статическим анализаторам проверять чистоту функций, но для композиции по-прежнему используются ручные реализации.

<?php
// Простые функции
function trimSpaces(string $s): string {
    return trim($s);
}

function toUpperCase(string $s): string {
    return strtoupper($s);
}

function addExclamation(string $s): string {
    return $s . '!';
}

// Функция композиции (слева направо)
function compose(callable ...$fns): Closure {
    return function ($initial) use ($fns) {
        return array_reduce($fns, function ($carry, $fn) {
            return $fn($carry);
        }, $initial);
    };
}

$processString = compose('trimSpaces', 'toUpperCase', 'addExclamation');
echo $processString('  hello world  '); // HELLO WORLD!
?>

5. Функторы и монады: практический пример с Maybe

Функторы — это структуры данных, которые реализуют метод map, применяющий функцию к содержимому. Монады расширяют эту идею методом bind (или flatMap), позволяя цепочки операций без вложенных проверок на null.

Реализация простого монадического типа Maybe в PHP:

<?php
class Maybe {
    private $value;
    
    private function __construct($value) {
        $this->value = $value;
    }
    
    public static function just($value): self {
        return new self($value);
    }
    
    public static function nothing(): self {
        return new self(null);
    }
    
    public function map(callable $fn): self {
        if ($this->value === null) {
            return self::nothing();
        }
        return self::just($fn($this->value));
    }
    
    public function bind(callable $fn): self {
        if ($this->value === null) {
            return self::nothing();
        }
        return $fn($this->value);
    }
    
    public function getValue() {
        return $this->value;
    }
}

// Использование
$result = Maybe::just('  PHP  ')                

Блоги

Рекомендаций по книгам