Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Functional PHP
https://github.com/lstrojny/functional-php
Admin
Functional PHP provides a set of functional programming primitives for PHP, inspired by Scala, Dojo,
...
Tokens:
43,303
Snippets:
330
Trust Score:
9.8
Update:
1 week ago
Context
Skills
Chat
Benchmark
90.2
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Functional PHP Functional PHP (`lstrojny/functional-php`) is a library of functional programming primitives for PHP, heavily inspired by Scala's traversable collections, Dojo's array functions, and Underscore.js. It provides a consistent, composable set of higher-order functions that work uniformly with both PHP arrays and any object implementing the `Traversable` interface. All functions live in the `Functional` namespace to avoid conflicts, and every collection callback consistently receives `($value, $index, $collection)` as its arguments. The library covers the full spectrum of functional programming needs: collection traversal and transformation (`map`, `filter`, `reduce`, `flatten`, `zip`), predicate testing (`every`, `some`, `none`), partial application and currying (`partial_left`, `partial_right`, `curry`, `curry_n`), function composition and decoration (`compose`, `flip`, `memoize`, `retry`, `tail_recursion`), mathematical aggregations (`sum`, `product`, `average`, `minimum`, `maximum`), and a suite of invocation helpers for safe method calls on objects. It is installed via Composer and requires PHP 7.1 or PHP 8+. --- ## Installation ```bash composer require lstrojny/functional-php ``` ```php // Preferred import style (PHP 5.6+) use function Functional\map; use function Functional\filter; // Or import the whole namespace as an alias use Functional as F; $result = F\map($collection, $callback); ``` --- ## Collection Traversal ### `map()` — Transform every element of a collection Applies a callback to each element and returns a new array of the results, preserving keys. ```php use function Functional\map; $users = [ ['name' => 'Alice', 'age' => 30], ['name' => 'Bob', 'age' => 25], ]; // Extract names $names = map($users, fn ($user) => $user['name']); // => ['Alice', 'Bob'] // Double every number $doubled = map([1, 2, 3, 4], fn ($n) => $n * 2); // => [2, 4, 6, 8] ``` --- ### `each()` — Iterate over a collection for side effects Applies a callback to every element purely for side effects; returns nothing. ```php use function Functional\each; $log = []; each(['a', 'b', 'c'], function ($value, $index) use (&$log) { $log[] = "[$index] => $value"; }); // $log: ['[0] => a', '[1] => b', '[2] => c'] ``` --- ### `select()` / `filter()` and `reject()` — Filter a collection `select()` (aliased as `filter()`) keeps elements where the callback returns truthy; `reject()` keeps elements where it returns falsy. Both preserve array keys. ```php use function Functional\select; use function Functional\reject; use function Functional\filter; // alias for select $numbers = [1, 2, 3, 4, 5, 6]; $evens = select($numbers, fn ($n) => $n % 2 === 0); // => [1 => 2, 3 => 4, 5 => 6] $odds = reject($numbers, fn ($n) => $n % 2 === 0); // => [0 => 1, 2 => 3, 4 => 5] // Without callback: keeps only truthy values $truthy = filter([0, 1, '', 'hello', null, true]); // => [1 => 1, 3 => 'hello', 5 => true] ``` --- ### `reduce_left()` and `reduce_right()` — Reduce a collection to a scalar `reduce_left()` folds from first to last; `reduce_right()` folds from last to first. The callback receives `($value, $index, $collection, $accumulator)`. ```php use function Functional\reduce_left; use function Functional\reduce_right; // Sum of all elements $sum = reduce_left([1, 2, 3, 4, 5], fn ($v, $i, $c, $acc) => $acc + $v, 0); // => 15 // Build a string left-to-right vs right-to-left $left = reduce_left([2, 3], fn ($v, $i, $c, $acc) => $acc . $v, '2'); // '223' $right = reduce_right([2, 3], fn ($v, $i, $c, $acc) => $acc . $v, '2'); // '232' // Flatten nested arrays manually $nested = [[1, 2], [3, 4], [5]]; $flat = reduce_left($nested, fn ($v, $i, $c, $acc) => array_merge($acc, $v), []); // => [1, 2, 3, 4, 5] ``` --- ### `flatten()` — Flatten a nested collection Recursively flattens a nested combination of arrays/Traversables into a single flat array. Does not preserve indexes. ```php use function Functional\flatten; $nested = [1, 2, [3, 4, [5, 6]], 7]; $flat = flatten($nested); // => [1, 2, 3, 4, 5, 6, 7] // Works with mixed types $mixed = ['a', ['b', ['c', 'd']], 'e']; flatten($mixed); // => ['a', 'b', 'c', 'd', 'e'] ``` --- ### `flat_map()` — Map then flatten one level Applies a callback to each element and flattens the result by one level. ```php use function Functional\flat_map; $sentences = ['hello world', 'foo bar']; $words = flat_map($sentences, fn ($s) => explode(' ', $s)); // => ['hello', 'world', 'foo', 'bar'] ``` --- ## Predicate Testing ### `every()` — Test if all elements satisfy a predicate Returns `true` if every element passes the callback. Without a callback, returns `true` if all values are truthy. ```php use function Functional\every; $ages = [21, 35, 18, 42]; $allAdults = every($ages, fn ($age) => $age >= 18); // => true $allOverThirty = every($ages, fn ($age) => $age > 30); // => false // No callback: all-truthy check every([1, 'yes', true]); // => true every([1, 0, true]); // => false ``` --- ### `some()` — Test if at least one element satisfies a predicate Returns `true` if at least one element passes the callback. Without a callback, returns `true` if any value is truthy. ```php use function Functional\some; $statuses = ['inactive', 'active', 'inactive']; $hasActive = some($statuses, fn ($s) => $s === 'active'); // => true $hasBanned = some($statuses, fn ($s) => $s === 'banned'); // => false ``` --- ### `none()` — Test if no elements satisfy a predicate Returns `true` if no element passes the callback. ```php use function Functional\none; $scores = [70, 85, 90]; $noPerfectScore = none($scores, fn ($s) => $s === 100); // => true $noFailingScore = none($scores, fn ($s) => $s < 60); // => true ``` --- ### `true()`, `false()`, `truthy()`, `falsy()` — Strict and loose all-match checks `true()` / `false()` use strict equality (`=== true` / `=== false`); `truthy()` / `falsy()` use loose truthiness. ```php use function Functional\true; use function Functional\false; use function Functional\truthy; use function Functional\falsy; true([true, true]); // => true true([true, 1]); // => false (1 !== true strictly) false([false, false]); // => true false([false, 0, null]); // => false (0 !== false strictly) truthy([true, 1, 'yes']); // => true truthy([true, 0, 'yes']); // => false falsy([false, 0, null, '']); // => true falsy([false, 'str', null]); // => false ``` --- ### `contains()` — Check for element membership Returns `true` if a collection contains the given value. Third parameter controls strict comparison (default: strict). ```php use function Functional\contains; contains(['a', 'b', 'c'], 'b'); // => true contains([1, 2, 3], '2'); // => false (strict) contains([1, 2, 3], '2', false); // => true (loose) contains(['x' => 1, 'y' => 2], 1); // => true ``` --- ## Searching & Indexing ### `first()` / `head()` and `last()` — Find first or last matching element Returns the first/last element passing the callback, or the first/last element overall when no callback is given. ```php use function Functional\first; use function Functional\last; $numbers = [3, 7, 2, 8, 1, 5]; $firstOver5 = first($numbers, fn ($n) => $n > 5); // => 7 $lastOver5 = last($numbers, fn ($n) => $n > 5); // => 8 // No callback: just first/last element first($numbers); // => 3 last($numbers); // => 5 ``` --- ### `first_index_of()`, `last_index_of()`, `indexes_of()` — Locate values by index Return the index (or all indexes) of a value within a collection. ```php use function Functional\first_index_of; use function Functional\last_index_of; use function Functional\indexes_of; $data = ['apple', 'banana', 'apple', 'cherry', 'apple']; first_index_of($data, 'apple'); // => 0 last_index_of($data, 'apple'); // => 4 indexes_of($data, 'apple'); // => [0, 2, 4] // indexes_of also accepts a predicate indexes_of($data, fn ($v) => strlen($v) > 5); // => [1, 3] ``` --- ### `pluck()` — Extract a property from every element Fetches a single property from each object or array in a collection. ```php use function Functional\pluck; $users = [ ['name' => 'Alice', 'age' => 30], ['name' => 'Bob', 'age' => 25], ['name' => 'Carol', 'age' => 35], ]; $names = pluck($users, 'name'); // => ['Alice', 'Bob', 'Carol'] $ages = pluck($users, 'age'); // => [30, 25, 35] ``` --- ### `pick()` — Pick a single element from a collection by key Returns the value at `$key`, or `$default` if the key does not exist. ```php use function Functional\pick; $config = ['host' => 'localhost', 'port' => 5432, 'name' => 'mydb']; pick($config, 'host'); // => 'localhost' pick($config, 'password'); // => null pick($config, 'password', 'secret'); // => 'secret' ``` --- ## Transformation ### `sort()` — Sort a collection with a comparator Sorts a collection using a user-defined comparison function. Optionally preserves array keys. ```php use function Functional\sort; $names = ['Charlie', 'Alice', 'Bob']; // Alphabetical $sorted = sort($names, fn ($a, $b) => strcmp($a, $b)); // => ['Alice', 'Bob', 'Charlie'] // Preserve keys $sorted = sort($names, fn ($a, $b) => strcmp($a, $b), true); // => [1 => 'Alice', 2 => 'Bob', 0 => 'Charlie'] // Sort objects by a field $users = [/* User objects */]; sort($users, fn ($a, $b) => $a->getAge() <=> $b->getAge()); ``` --- ### `group()` — Group a collection by a key Splits a collection into sub-arrays grouped by the key returned from the callback. ```php use function Functional\group; $people = [ ['name' => 'Alice', 'dept' => 'Engineering'], ['name' => 'Bob', 'dept' => 'Marketing'], ['name' => 'Carol', 'dept' => 'Engineering'], ['name' => 'Dave', 'dept' => 'Marketing'], ]; $byDept = group($people, fn ($p) => $p['dept']); // => [ // 'Engineering' => [['name'=>'Alice',...], ['name'=>'Carol',...]], // 'Marketing' => [['name'=>'Bob',...], ['name'=>'Dave',...]] // ] ``` --- ### `partition()` — Split a collection by multiple predicates Distributes each element into the bucket of the first matching predicate; elements matching none go into a final catch-all bucket. ```php use function Functional\partition; $numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; [$divisibleBy3, $divisibleBy2, $rest] = partition( $numbers, fn ($n) => $n % 3 === 0, fn ($n) => $n % 2 === 0 ); // $divisibleBy3 => [3, 6, 9] // $divisibleBy2 => [2, 4, 8, 10] (already matched 6 went to first bucket) // $rest => [1, 5, 7] ``` --- ### `zip()` and `zip_all()` — Combine multiple arrays by position Merges elements from multiple arrays positionally, optionally through a combining callback. `zip()` uses the keys of the first array; `zip_all()` covers all keys present. ```php use function Functional\zip; // Without callback: creates pairs zip(['a', 'b', 'c'], [1, 2, 3]); // => [['a', 1], ['b', 2], ['c', 3]] // With callback: merge into a string zip( ['one', 'two', 'three'], [1, 2, 3], fn ($word, $num) => "$word=$num" ); // => ['one=1', 'two=2', 'three=3'] // Zip three arrays zip([1, 2], ['a', 'b'], [true, false]); // => [[1, 'a', true], [2, 'b', false]] ``` --- ### `unique()` — Remove duplicate values Returns a de-duplicated array, using an optional indexer callback to determine uniqueness. ```php use function Functional\unique; unique([1, 2, 2, 3, 3, 3]); // => [1, 2, 3] // Custom indexer: unique by first letter $words = ['apple', 'avocado', 'banana', 'blueberry', 'cherry']; unique($words, fn ($w) => $w[0]); // => ['apple', 'banana', 'cherry'] (first match per initial letter) ``` --- ### `flatten()` + `intersperse()` + `entries()` / `from_entries()` — Structural transforms ```php use function Functional\intersperse; use function Functional\entries; use function Functional\from_entries; // Insert separator between elements intersperse(['a', 'b', 'c'], '-'); // => ['a', '-', 'b', '-', 'c'] // Convert associative array to pairs and back $map = ['one' => 1, 'two' => 2, 'three' => 3]; $pairs = entries($map); // => [['one', 1], ['two', 2], ['three', 3]] $back = from_entries($pairs); // => ['one' => 1, 'two' => 2, 'three' => 3] ``` --- ### `select_keys()` and `omit_keys()` — Key-based array slicing Return a sub-array keeping only specified keys (`select_keys`) or excluding them (`omit_keys`). ```php use function Functional\select_keys; use function Functional\omit_keys; $data = ['id' => 1, 'name' => 'Alice', 'password' => 'secret', 'role' => 'admin']; // Keep only safe fields $safe = select_keys($data, ['id', 'name', 'role']); // => ['id' => 1, 'name' => 'Alice', 'role' => 'admin'] // Remove sensitive fields $public = omit_keys($data, ['password']); // => ['id' => 1, 'name' => 'Alice', 'role' => 'admin'] ``` --- ### `take_left()` and `take_right()` — Slice from beginning or end ```php use function Functional\take_left; use function Functional\take_right; take_left([1, 2, 3, 4, 5], 3); // => [1, 2, 3] take_right([1, 2, 3, 4, 5], 3); // => [3, 4, 5] // Preserve original keys when taking from right take_right(['a', 'b', 'c', 'd'], 2, true); // => [2 => 'c', 3 => 'd'] ``` --- ### `drop_first()` and `drop_last()` — Drop elements while predicate is true Drops elements from the beginning or end of a collection while the callback returns truthy. ```php use function Functional\drop_first; use function Functional\drop_last; $numbers = [1, 2, 3, 4, 5]; // Drop while index < 2 (drop first 2 elements) drop_first($numbers, fn ($v, $i) => $i < 2); // => [2 => 3, 3 => 4, 4 => 5] // Keep only first 3 elements drop_last($numbers, fn ($v, $i) => $i < 3); // => [0 => 1, 1 => 2, 2 => 3] ``` --- ### `reindex()` — Rekey a collection by a callback Returns the collection with keys replaced by the values returned from the callback. ```php use function Functional\reindex; $users = [ ['id' => 'u1', 'name' => 'Alice'], ['id' => 'u2', 'name' => 'Bob'], ]; $byId = reindex($users, fn ($user) => $user['id']); // => ['u1' => ['id'=>'u1','name'=>'Alice'], 'u2' => ['id'=>'u2','name'=>'Bob']] ``` --- ### `frequencies()` — Count occurrences of each value Returns an array mapping each distinct value (or derived key) to its count. ```php use function Functional\frequencies; $votes = ['yes', 'no', 'yes', 'yes', 'no', 'abstain']; frequencies($votes); // => ['yes' => 3, 'no' => 2, 'abstain' => 1] // Group by string length $words = ['cat', 'dog', 'elephant', 'ant', 'bee']; frequencies($words, fn ($w) => strlen($w)); // => [3 => 3, 8 => 1, 3 => ...] => [3 => 3, 8 => 1] ``` --- ## Partial Application ### `partial_left()` and `partial_right()` — Pre-fill arguments `partial_left()` pre-fills arguments from the left; `partial_right()` pre-fills from the right. ```php use function Functional\partial_left; use function Functional\partial_right; use function Functional\select; $subtract = fn ($a, $b) => $a - $b; // partial_left: fixes first argument $subtractFrom20 = partial_left($subtract, 20); $subtractFrom20(5); // => 15 // partial_right: fixes last argument $subtract5 = partial_right($subtract, 5); $subtract5(20); // => 15 // Practical: pre-fill a filter $names = ['alice', 'bob', 'alfred', 'carol']; $startsWith = fn ($needle, $str) => str_starts_with($str, $needle); $startsWithA = partial_left($startsWith, 'a'); $aNames = select($names, $startsWithA); // => ['alice', 'alfred'] ``` --- ### `partial_any()` — Partial application with placeholders Uses the `…` constant (or `placeholder()` / `Functional\…()`) to mark which positions are left open. ```php use function Functional\partial_any; use function Functional\select; use const Functional\…; $elements = ['john', 'joe', 'joanna', 'patrick']; // Fill second argument of substr_count, leave first as placeholder $containsJo = partial_any('substr_count', …, 'jo'); $matches = select($elements, $containsJo); // => ['john', 'joe', 'joanna'] // Multiple placeholders $format = fn ($prefix, $value, $suffix) => $prefix . $value . $suffix; $brackets = partial_any($format, '[', …, ']'); $brackets('hello'); // => '[hello]' ``` --- ### `partial_method()` — Partially apply a method call Returns a callback that calls the named method on whatever object is passed to it. ```php use function Functional\partial_method; use function Functional\select; use function Functional\map; // Filter by object method $activeUsers = select($users, partial_method('isActive')); // Map method with arguments $setAdmin = partial_method('setRole', ['admin']); map($users, $setAdmin); // calls $user->setRole('admin') on each user ``` --- ### `ary()` — Limit argument count passed to a function Wraps a callable and only passes the first `$count` arguments to it (or last if negative). ```php use function Functional\ary; use function Functional\map; // map passes ($value, $index, $collection) - ucfirst only accepts 1 arg $result = map(['hello', 'world'], ary('ucfirst', 1)); // => ['Hello', 'World'] // array_keys also only needs 1 argument $onlyValues = map([[1,2],[3,4]], ary('array_values', 1)); ``` --- ## Currying ### `curry()` — Auto-curry a function Returns a curried version of a function, inferring the arity via reflection. ```php use function Functional\curry; $add = fn ($a, $b, $c) => $a + $b + $c; $curriedAdd = curry($add); $add10 = $curriedAdd(10); // partially applied $add10and5 = $add10(5); // still partially applied $add10and5(3); // => 18 // Fluent style (PHP 7+) curry(fn ($a, $b, $c) => $a + $b + $c)(10)(5)(3); // => 18 // Include optional parameters in currying $greet = fn ($greeting, $name, $punctuation = '!') => "$greeting, $name$punctuation"; $curriedWithOpt = curry($greet, false); // false = include optional params $curriedWithOpt('Hello')('Alice')('.'); // => 'Hello, Alice.' ``` --- ### `curry_n()` — Curry with explicit arity Like `curry()` but with a manually specified number of arguments to curry. Required when using flipped functions or variadic callables. ```php use function Functional\curry_n; use function Functional\flip; // Curry only first 2 of 4 parameters $addFour = fn ($a, $b, $c, $d) => $a + $b + $c + $d; $curriedAdd2 = curry_n(2, $addFour); $add10 = $curriedAdd2(10); $add10(5, 1, 1); // => 17 (passes remaining args directly) // Required for flipped functions $filter = curry_n(2, flip('Functional\select')); $getEvens = $filter(fn ($n) => $n % 2 === 0); $getEvens([1, 2, 3, 4, 5, 6]); // => [2, 4, 6] ``` --- ## Function Composition & Decoration ### `compose()` — Chain functions into a pipeline Returns a new function that passes its argument through each function from left to right. ```php use function Functional\compose; $plus2 = fn ($x) => $x + 2; $times3 = fn ($x) => $x * 3; $square = fn ($x) => $x ** 2; $transform = compose($plus2, $times3, $square); $transform(4); // ((4 + 2) * 3) ^ 2 = 324 // Practical: sanitize and normalize user input $sanitize = compose('trim', 'strtolower', fn ($s) => preg_replace('/\s+/', '-', $s)); $sanitize(' Hello World '); // => 'hello-world' ``` --- ### `flip()` — Swap argument order Returns a new function with its first two arguments reversed. Useful for point-free style with currying. ```php use function Functional\flip; use function Functional\curry_n; // str_contains($haystack, $needle) -> flip -> ($needle, $haystack) $needleFirst = flip('str_contains'); $needleFirst('hello', 'hello world'); // => true // Build a reusable filter using flip + curry_n $filter = curry_n(2, flip('Functional\select')); $getPositive = $filter(fn ($n) => $n > 0); $getPositive([-1, 2, -3, 4]); // => [2, 4] ``` --- ### `not()` — Negate a predicate Returns a new function that is the logical negation of its input callable. ```php use function Functional\not; use function Functional\select; $isEven = fn ($n) => $n % 2 === 0; $isOdd = not($isEven); $isOdd(3); // => true $isOdd(4); // => false // Use with select to filter odd numbers select([1, 2, 3, 4, 5], $isOdd); // => [0=>1, 2=>3, 4=>5] ``` --- ### `juxt()` — Apply multiple functions to the same arguments Takes a list of functions and returns a new function that applies all of them and collects their results. ```php use function Functional\juxt; $stats = juxt('min', 'max', 'array_sum'); $stats(3, 1, 4, 1, 5, 9); // => [1, 9, 23] $transformString = juxt('strtoupper', 'strtolower', 'strrev', 'strlen'); $transformString('Hello'); // => ['HELLO', 'hello', 'olleH', 5] ``` --- ### `converge()` — Fork-join function composition Passes arguments to branching functions, then feeds all their results to a converging function. ```php use function Functional\converge; // Classic average: sum / count $average = converge( fn ($sum, $count) => $sum / $count, ['array_sum', 'count'] ); $average([10, 20, 30, 40]); // => 25.0 // Compute min-max range $range = converge( fn ($min, $max) => $max - $min, ['min', 'max'] ); $range(3, 1, 4, 1, 5, 9); // => 8 ``` --- ### `memoize()` — Cache function results Caches the return value of a function call so repeated calls with the same arguments return the cached result. Pass `null` as callback to clear the cache. ```php use function Functional\memoize; $fibonacci = function (int $n) use (&$fibonacci): int { if ($n <= 1) return $n; return memoize($fibonacci, [$n - 1]) + memoize($fibonacci, [$n - 2]); }; memoize($fibonacci, [10]); // => 55 (computed once, cached) memoize($fibonacci, [10]); // => 55 (returned from cache) // Explicit cache key $result = memoize(fn () => expensiveApiCall(), [], 'my-api-cache-key'); // Clear all memoized values memoize(null); ``` --- ### `tail_recursion()` — Safe deep recursion via trampolining Decorates a recursive function to use trampolining, preventing stack overflows on deep recursion. ```php use function Functional\tail_recursion; $factorial = tail_recursion(function (int $n, int $acc = 1) use (&$factorial): int { if ($n <= 1) return $acc; return $factorial($n - 1, $n * $acc); }); $factorial(10); // => 3628800 $factorial(10000); // No stack overflow // Sum a large range $sumOfRange = tail_recursion(function ($from, $to, $acc = 0) use (&$sumOfRange) { if ($from > $to) return $acc; return $sumOfRange($from + 1, $to, $acc + $from); }); $sumOfRange(1, 10000); // => 50005000 ``` --- ### `capture()` — Capture a function's return value by reference Wraps a callback so that its return value is stored in a reference variable while still being returned normally. ```php use function Functional\capture; $result = null; $fn = capture(fn () => ['status' => 'ok', 'data' => [1, 2, 3]], $result); $fn(); var_dump($result); // ['status' => 'ok', 'data' => [1, 2, 3]] ``` --- ## Retry and Polling ### `retry()` — Retry a callback on exception Retries a callback up to `$retryCount` times, pausing between attempts using a sequence generator. ```php use function Functional\retry; use function Functional\sequence_exponential; use function Functional\sequence_linear; use function Functional\sequence_constant; // Retry an HTTP request up to 5 times with exponential back-off (100ms base) retry( function () use ($client, $url) { $response = $client->get($url); if ($response->getStatusCode() >= 500) { throw new \RuntimeException('Server error'); } return $response; }, 5, sequence_exponential(1, 100) // delays: 100ms, 200ms, 400ms, 800ms, 1600ms ); // Linear back-off: 200ms, 400ms, 600ms retry($fn, 3, sequence_linear(200, 200)); // Constant delay: always 500ms retry($fn, 3, sequence_constant(500)); ``` --- ### `poll()` — Poll until truthy or timeout Repeatedly calls a callback until it returns a truthy value or a microsecond timeout is reached. ```php use function Functional\poll; use function Functional\sequence_linear; // Wait up to 5 seconds for a lock file to appear, checking every 100ms $acquired = poll( fn () => file_exists('/tmp/lockfile'), 5_000_000, // 5 seconds in microseconds sequence_linear(100_000, 0) // check every 100ms ); if (!$acquired) { throw new \RuntimeException('Timed out waiting for lock'); } // Wait for a queue to have items poll( fn () => $queue->size() > 0, 10_000_000, sequence_exponential(1, 50_000) // 50ms, 100ms, 200ms... ); ``` --- ## Conditional Functions ### `if_else()` — Conditional function dispatch Returns a new function that calls `$then` or `$else` based on the result of the `$if` predicate, all with the same argument. ```php use function Functional\if_else; use function Functional\greater_than; use function Functional\map; $classify = if_else( greater_than(0), fn ($n) => "positive: $n", fn ($n) => "non-positive: $n" ); $classify(5); // => 'positive: 5' $classify(-3); // => 'non-positive: -3' $labels = map([-2, 0, 3, 7, -1], $classify); // => ['non-positive: -2', 'non-positive: 0', 'positive: 3', 'positive: 7', 'non-positive: -1'] ``` --- ### `matching()` — Pattern-match style dispatch Returns a function that tests each `[$predicate, $handler]` pair in order and calls the handler of the first matching predicate. ```php use function Functional\matching; use function Functional\greater_than_or_equal; $classify = matching([ [greater_than_or_equal(90), fn ($s) => 'A'], [greater_than_or_equal(80), fn ($s) => 'B'], [greater_than_or_equal(70), fn ($s) => 'C'], [greater_than_or_equal(60), fn ($s) => 'D'], [fn () => true, fn ($s) => 'F'], ]); $classify(95); // => 'A' $classify(73); // => 'C' $classify(55); // => 'F' ``` --- ## Invocation Helpers ### `invoke()`, `invoke_first()`, `invoke_last()` — Call a method on collection items `invoke()` calls a method on every object; `invoke_first()` / `invoke_last()` call it on the first/last object that has the method. ```php use function Functional\invoke; use function Functional\invoke_first; use function Functional\invoke_last; // Call a method on all objects $emails = invoke($users, 'getEmail'); // => ['alice@example.com', 'bob@example.com', ...] // Call with arguments invoke($meetings, 'setStatus', ['confirmed']); // invoke_first: finds first object with the method $events = [new MandatoryEvent(), new OptionalEvent(), new OptionalEvent()]; invoke_first($events, 'reschedule', ['+1 day']); // calls on first OptionalEvent invoke_last($events, 'cancel'); // calls on last OptionalEvent ``` --- ### `invoke_if()` — Safely call a method on a possibly-null object Calls a method on an object if it exists and the object is non-null; returns a default value otherwise. ```php use function Functional\invoke_if; // Safe method call; returns 0 if $user is null or lacks getId() $userId = invoke_if($user, 'getId', [], 0); // With arguments $formatted = invoke_if($dateTime, 'format', ['Y-m-d'], 'N/A'); ``` --- ### `invoker()` — Create a reusable method-call callable Returns a callable that invokes the specified method (with optional arguments) on whatever object it receives. ```php use function Functional\invoker; use function Functional\map; $getEmail = invoker('getEmail'); $emails = map($users, $getEmail); // => ['alice@example.com', 'bob@example.com'] $activate = invoker('setStatus', ['active']); map($users, $activate); // calls $user->setStatus('active') on each ``` --- ### `with()` and `tap()` — Null-safe callback invocation `with()` calls a callback on a value only if it is not `null`, returning the callback's result. `tap()` calls a callback on a value and returns the original value (for side effects in chains). ```php use function Functional\with; use function Functional\tap; // with(): call callback only if value is not null $result = with(findUserById(42), function ($user) { sendWelcomeEmail($user); return $user->getId(); }); // If user not found (null), $result is null without error // tap(): side effect then return original value $user = tap(createUser('Alice'), fn ($u) => log("Created user: {$u->getName()}")); $user->activate(); // $user is the original created user ``` --- ## Mathematical Functions ### Aggregation: `sum()`, `product()`, `difference()`, `ratio()`, `average()`, `minimum()`, `maximum()` ```php use function Functional\sum; use function Functional\product; use function Functional\difference; use function Functional\ratio; use function Functional\average; use function Functional\minimum; use function Functional\maximum; $numbers = [2, 3, 4, 5]; sum($numbers); // => 14 (0 + 2 + 3 + 4 + 5) sum($numbers, 10); // => 24 (start from 10) product($numbers); // => 120 (1 * 2 * 3 * 4 * 5) difference($numbers); // => -14 (0 - 2 - 3 - 4 - 5) ratio($numbers); // => 1/120 average($numbers); // => 3.5 minimum($numbers); // => 2 maximum($numbers); // => 5 ``` --- ## Higher-Order Comparison ### `compare_on()` and `compare_object_hash_on()` — Build comparators for sorting ```php use function Functional\compare_on; use function Functional\compare_object_hash_on; $users = [/* User objects with getName() */]; // Sort by name using strcmp usort($users, compare_on('strcmp', fn ($u) => $u->getName())); // Sort by object hash (de-duplicate, detect identity) $diff = array_udiff($setA, $setB, compare_object_hash_on()); ``` --- ## Utility Functions ### `repeat()`, `noop()`, `const_function()`, `id()`, `concat()` — Utility combinators ```php use function Functional\repeat; use function Functional\noop; use function Functional\const_function; use function Functional\id; use function Functional\concat; // repeat: run a closure N times repeat(fn () => echo 'ping ')(3); // prints: ping ping ping // noop: placeholder callable that does nothing array_walk($items, noop()); // const_function: always returns the same value $alwaysTrue = const_function(true); $alwaysTrue(); // => true $alwaysTrue('anything'); // => true // id: identity function, returns its argument unchanged id(42); // => 42 id('hello'); // => 'hello' // concat: string concatenation as a function concat('Hello', ', ', 'World', '!'); // => 'Hello, World!' ``` --- ## Summary Functional PHP is best suited for building data pipelines in PHP applications where collections of objects or arrays need to be transformed, filtered, and aggregated in a readable, composable manner. Common use cases include processing API response collections with `map` and `pluck`, building data access layers with `group`, `partition`, and `unique`, implementing resilient service clients with `retry` and `poll`, and writing clean domain-logic predicates with `every`, `some`, and `none`. The library integrates naturally into any PHP application that uses Composer, requiring no framework and no configuration. Integration patterns centre on composing small functions rather than writing large imperative loops. Functions like `compose`, `curry`, `partial_left`, and `flip` allow building reusable, point-free data transformers that can be passed directly into collection functions. The consistent three-argument callback signature `($value, $index, $collection)` across all collection functions makes handlers interchangeable, and the `Functional` class provides every function as a string constant (`Functional::map`, `Functional::filter`, etc.) for use wherever a callable string is required. This makes functional-php an easy drop-in complement to existing Laravel, Symfony, or plain PHP codebases without any architectural commitment.