Functional Programming in PHP: From Theory to Practice
PHP is traditionally associated with imperative and object-oriented approaches, but the modern language provides powerful tools for functional programming (FP). In this article, we will explore how to apply FP principles in PHP: from anonymous functions to currying and immutability. You will learn how to make code more predictable, testable, and concise.
1. Basics: Anonymous Functions and Closures
Anonymous functions (lambdas) are the cornerstone of FP in PHP. They allow passing behavior as data.
<?php// Simple anonymous function$greet = function($name) { return "Hello, $name!";};echo $greet('World'); // Hello, World!
// Usage with 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 are anonymous functions that capture variables from the outer scope using the use keyword:
<?php$factor = 3;$multiply = function($n) use ($factor) { return $n * $factor;};echo $multiply(5); // 15
// Closure with a mutable variable (by reference)$counter = 0;$increment = function() use (&$counter) { $counter++;};$increment();echo $counter; // 1?>Closures are indispensable when creating callback functions and factories.
2. Higher-Order Functions: array_map, array_filter, array_reduce
PHP has built-in classic higher-order functions that accept other functions as arguments:
array_map— transforms each element of an array;array_filter— filters elements based on a condition;array_reduce— reduces an array to a single value.
<?php$data = [1, 2, 3, 4, 5, 6];
// Filtering even numbers$even = array_filter($data, function($n) { return $n % 2 === 0;}); // [2, 4, 6]
// Doubling each element$doubled = array_map(function($n) { return $n * 2;}, $even); // [4, 8, 12]
// Sum of all values$sum = array_reduce($doubled, function($carry, $n) { return $carry + $n;}, 0); // 24?>Note: array_filter preserves keys. If re-indexing is needed, use array_values().
3. Immutability and Working with Data
Functional programming encourages immutability — data that cannot be changed. Instead of modifying existing objects, new ones are created.
<?php// Imperative approach (mutation)$user = ['name' => 'Ivan', 'age' => 25];$user['age'] = 26; // modifies the original array
// Functional approach (immutable)$user = ['name' => 'Ivan', 'age' => 25];$updatedUser = array_merge($user, ['age' => 26]); // new array?>For working with arrays, use array_merge, array_slice, and the spread operator (PHP 7.4+):
<?php$original = [1, 2, 3];$new = [...$original, 4]; // [1, 2, 3, 4] — original remains unchanged?>Immutability simplifies debugging and makes code thread-safe.
4. Currying and Partial Application
Currying is the transformation of a function with multiple arguments into a sequence of functions with a single argument. Partial application is fixing some of the arguments.
PHP does not have built-in currying, but it is easy to implement:
<?php// Curried addition functionfunction curryAdd($a) { return function($b) use ($a) { return function($c) use ($a, $b) { return $a + $b + $c; }; };}
$add5 = curryAdd(5);