Installs: 12 697
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 4
Forks: 1
Open Issues: 10
Requires
- php: ~7.1
Requires (Dev)
- phpunit/phpunit: ^6.2
- satooshi/php-coveralls: ^1.0.2
- squizlabs/php_codesniffer: ^3.0
This package is auto-updated.
Last update: 2024-01-29 03:07:18 UTC
README
This library provides tools to write more functional PHP code. Its concise and consistent API makes you more productive in different ways:
- Universal tools for processing iterables like arrays.
- Callback generation and modification functions.
- Optional value types to aid with stronger typing and erorr handling.
- Shorthand exception handling.
Table of contents
About
This library was written to address several concerns:
- Provide a single, consistent API to the different iterable types in PHP, and the different operations available to the individual types: one API, any iterable, always associative, access to keys.
- Provide iterable processing operations that do not yet exist in PHP.
- Make writing closures quick and easy. Predicate factories can be used to generate common (filter) conditions.
- Allow developers to create functions that easily distinguish between different function outputs using
optional value types. These can be used to solve problems like with
json_decode()
, which returnsNULL
in case of an error, or when it successfully decodes the JSON stringnull
. It is impossible to distinguish between the different outcomes without additional code, such as option types. - Use native PHP features where possible for improved interoperability and performance. Naming and parameter order
follow the predominant conventions in PHP. This means all iterators implement
\Iterator
, and many PHP core functions are used internally. - Add laziness where possible, so many operations are only applied to the iterator items you actually use.
Installation
Run composer require bartfeenstra/fu
in your project's root directory.
Usage
To use any of the code, you must first import the namespaces at the top of your files:
<?php use BartFeenstra\Functional as F; use BartFeenstra\Functional\Iterable as I; use BartFeenstra\Functional\Predicate as P; use function BartFeenstra\Functional\Iterable\iter; ?>
Iterators
Traversable/iterable data structures can be converted to universal iterators:
<?php // Arrays. $iterator = iter([3, 1, 4]); // \Traversable (includes native/Spl iterators). $iterator = iter(new \ArrayIterator([3, 1, 4])); // Callables that (return callables that...) return iterators. $callable = function (){ return function () { return iter([]); }; }; $iterator = iter($callable); // Existing universal iterators are passed through. $iterator = iter([]); assert($iterator === iter($iterator)); // Objects can expose universal iterators as well. $toIterator = new class() implements I\ToIterator { public function iter(): I\Iterator { return iter([]); } }; $iterator = iter($toIterator); ?>
Operations
The following operations work with iterator values, and even keys in the case of user-supplied callbacks:
each
Executes code for every value.
<?php $carrier = []; $list = [3, 1, 4]; iter($list)->each(function (int $i) use (&$carrier) { $carrier[] = $i; }); assert($list === $carrier); ?>
filter
Filters out values that do not match.
<?php $result = iter([3, 1, 4])->filter(P\gt(2)); assert([0 => 3, 2 => 4] === $result->toArray()); ?>
find
Tries to find a single matching value.
<?php $found = iter([3, 1, 4, 1, 5, 9])->find(P\gt(4)); assert(new I\SomeItem(5, 4) == $found); ?>
map
Converts all values individually.
<?php $original = [3, 1, 4]; $expected = [9, 3, 12]; $result = iter($original)->map(function (int $i): int { return 3 * $i; }); assert($expected === $result->toArray()); ?>
mapKeys
Converts all keys individually.
<?php $original = [ 3 => 'c', 1 => 'a', 4 => 'd', ]; $expected = [ 9 => 'c', 3 => 'a', 12 => 'd', ]; $result = iter($original)->mapKeys(function (string $value, int $key): int { return 3 * $key; }); assert($expected === $result->toArray()); ?>
reduce
Combines all values into a single one.
<?php $list = [3, 1, 4]; $sum = iter($list)->reduce(function (int $sum, int $item): int { return $sum + $item; }); assert(new F\SomeValue(8) == $sum); ?>
To terminate the reduction before all items have been processed, throw a TerminateReduction
with the final carrier
value.
fold
Combines all values into a single one, with a default start value.
<?php $start = 2; $list = [3, 1, 4]; $total = iter($list)->fold(function (int $total, int $item): int { return $total + $item; }, $start); assert(10 === $total); ?>
To terminate the fold before all items have been processed, throw a TerminateFold
with the final carrier value.
take
Takes n values.
<?php $start = 2; $list = [3, 1, 4, 1, 5, 9]; $result = iter($list)->take(4); assert([3, 1, 4, 1] === $result->toArray()); ?>
takeWhile
Take as many consecutively matching values as possible from the beginning.
<?php $start = 2; $list = [3, 1, 4, 1, 5, 9]; $result = iter($list)->takeWhile(P\le(3)); assert([3, 1] === $result->toArray()); ?>
slice
Slices the values into a smaller collection.
<?php $start = 2; $list = [3, 1, 4, 1, 5, 9]; $result = iter($list)->slice(2, 3); assert([2 => 4, 3 => 1, 4 => 5] === $result->toArray()); ?>
min
Gets the lowest value.
<?php $list = [3, 1, 4, 1, 5, 9]; $min = iter($list)->min(); assert(new F\SomeValue(1) == $min); ?>
max
Gets the highest value.
<?php $list = [3, 1, 4, 1, 5, 9]; $max = iter($list)->max(); assert(new F\SomeValue(9) == $max); ?>
sum
Sums all values.
<?php $list = [3, 1, 4, 1, 5, 9]; $sum = iter($list)->sum(); assert(new F\SomeValue(23) == $sum); ?>
forever
Infinitely repeats the set of values.
<?php $list = [3, 1, 4]; $iterator = iter($list)->forever(); $expected = [3, 1, 4, 3, 1, 4, 3]; assert($expected === iterator_to_array($iterator->take(7), false)); ?>
zip
Combines the values of two or more iterables into tuples.
<?php $one = [3, 1, 4]; $two = [1, 5, 9]; $three = [2, 9, 2]; $zip = iter($one)->zip($two, $three); $expected = [[3, 1, 2], [1, 5, 9], [4, 9, 2]]; assert($expected === iterator_to_array($zip)); ?>
list
Converts all keys to integers, starting from 0.
<?php $array = [ 'a' => 'A', 'b' => 'B', 'c' => 'C', ]; $indexed = iter($array)->list(); $expected = ['A', 'B', 'C']; assert($expected === iterator_to_array($indexed)); ?>
listKeys
Uses keys as values, and indexes them from 0.
<?php $array = [ 'a' => 'A', 'b' => 'B', 'c' => 'C', ]; $keys = iter($array)->listKeys(); $expected = ['a', 'b', 'c']; assert($expected === iterator_to_array($keys)); ?>
flip
Swaps keys and values, similarly to array_flip()
.
<?php $array = [ 'a' => 3, 'b' => 1, 'c' => 4, ]; $flipped = iter($array)->flip(); $expected = [ 3 => 'a', 1 => 'b', 4 => 'c', ]; assert($expected === $flipped->toArray()); ?>
reverse
Reverses the order of the values.
<?php $array = [3, 1, 4]; $reverse = iter($array)->reverse(); assert([4, 1, 3] === $reverse->toArray()); ?>
first
Gets the first value.
<?php $array = [3, 1, 4, 1, 5, 9]; assert(new I\SomeItem(3, 0) == iter($array)->first()); ?>
last
Gets the last value.
<?php $array = [3, 1, 4, 1, 5, 9]; assert(new I\SomeItem(9, 5) == iter($array)->last()); ?>
empty
Checks if there are no values.
<?php assert(TRUE === iter([])->empty()); assert(FALSE === iter([3, 1, 4])->empty()); ?>
sort
Sorts items by their values.
<?php $array = [ 3 => 'c', 1 => 'a', 4 => 'd', ]; // ::sort() also takes an optional custom comparison callable. $sort = iter($array)->sort(); $expected = [ 1 => 'a', 3 => 'c', 4 => 'd', ]; assert($expected === iterator_to_array($sort)); ?>
sortKeys
Sorts items by their keys.
<?php $array = [ 'c' => 3, 'a' => 1, 'd' => 4, ]; // ::sortKeys() also takes an optional custom comparison callable. $sort = iter($array)->sortKeys(); $expected = [ 'a' => 1, 'c' => 3, 'd' => 4, ]; assert($expected === iterator_to_array($sort)); ?>
chain
Chains other iterables to an existing iterator, and re-indexes the values.
<?php $arrayOne = [3, 1, 4]; $arrayTwo = [1, 5, 9]; $arrayThree = [2, 6, 5]; $iterator = iter($arrayOne)->chain($arrayTwo, $arrayThree); $expected = [3, 1, 4, 1, 5, 9, 2, 6, 5]; assert($expected === $iterator->toArray()); ?>
flatten
Flattens the iterables contained by an iterator into a single new iterator.
<?php $array = [ [3, 1, 4], [1, 5, 9], [2, 6, 5], ]; $iterator = iter($array)->flatten(); $expected = [3, 1, 4, 1, 5, 9, 2, 6, 5]; assert($expected === $iterator->toArray()); ?>
unique
Removes all duplicate values.
<?php $objectOne = new \stdClass(); $objectTwo = new \stdClass(); $array = [0, false, false, null, [], [], '0', $objectOne, $objectOne, $objectTwo]; $iterator = iter($array)->unique(); $expected = [ 0 => 0, 1 => false, 3 => null, 4 => [], 6 => '0', 7 => $objectOne, 9 => $objectTwo, ]; assert($expected === $iterator->toArray()); ?>
Exception handling
Complex try
/catch
blocks can be replaced and converted to Result
easily:
<?php // Try executing a callable, catch all exceptions, and output a Result. $result = F\try_except(function () {/** ... */}); // Try executing a callable, catch all Foo, Bar, Baz, and Qux exceptions, and output a Result. $result = F\try_except(function () {/** ... */}, Foo::class, Bar::class, Baz::class, Qux::class); // Try executing a callable at most twice, catch all exceptions, and output a Result. $result = F\retry_except(function () {/** ... */}); // Try executing a callable at most 5 times, catch all Foo, Bar, Baz, and Qux exceptions, and output a Result. $result = F\retry_except(function () {/** ... */}, 5, Foo::class, Bar::class, Baz::class, Qux::class); ?>
Predicates
Predicates can be used with filter()
and find()
. They can be any
callable that takes a
single parameter and returns a boolean, but we added some shortcuts for common
conditions. These functions take configuration parameters, and return
predicates.
<?php // All values strictly identical to TRUE. $predicate = P\true(); // All values strictly identical to FALSE. $predicate = P\false(); // All values that evaluate to TRUE. $predicate = P\truthy(); // All values that evaluate to FALSE. $predicate = P\falsy(); // All values strictly identical to 0. $predicate = P\id(0); // All values equal to "Apples and oranges". $predicate = P\eq('Apples and oranges'); // All values greater than 9. $predicate = P\gt(9); // All values greater than or equal to 99. $predicate = P\ge(99); // All values lesser than 15. $predicate = P\lt(15); // All values lesser than or equal to 666. $predicate = P\le(666); // All values that are instances of Foo, Bar, Baz, or Qux. $predicate = P\instance_of(Foo::class, Bar::class, Baz::class, Qux::class); // One or more values are lesser than 0 OR greater than 9. $predicate = P\any(P\lt(0), P\gt(9)); // All values are greater than 0 AND lesser than 9. $predicate = P\all(P\gt(0), P\lt(9)); // All values different from "Apples and oranges". $predicate = P\not(P\eq('Apples and oranges')); ?>
The Option
type
In PHP, NULL
signifies the absence of a value, but it is also used as a value itself. In such cases, an Option
type
helps to distinguish between NULL
as a value, and no value at all.
<?php use BartFeenstra\Functional\Option; use BartFeenstra\Functional\Some; use BartFeenstra\Functional\SomeValue; use BartFeenstra\Functional\None; function get_option(): Option { if (true) { return new SomeValue(666); } return new None(); } function handle_option(Option $value) { if ($value instanceof Some) { print sprintf('The value is %s.', $value()); } // $value is an instance of None. else { print 'No value could be retrieved.'; } } handle_option(get_option()); ?>
The Result
type
The Result
type can be used to complement or replace exceptions. As such, it is returned by
functions like try_except()
. It represents success and a value, or an error.
<?php use BartFeenstra\Functional\Ok; use BartFeenstra\Functional\OkValue; use BartFeenstra\Functional\Result; use BartFeenstra\Functional\ThrowableError; function get_result(): Result { try { // Do some things that may throw a ResultComputationException. return new OkValue(666); } catch (\ResultComputationException $e) { return new ThrowableError($e); } } function handle_result(Result $result) { if ($result instanceof Ok) { print sprintf('The value is %s.', $result()); } // $value is an instance of Error. else { print sprintf('An error occurred: %s.', $result); } } handle_result(get_result()); ?>
Partial function application
Partial function application is the creation of a new function with zero or more parameters, based on an existing function, by fixing one or more of the arguments of the original function, before calling it. Practically speaking, it allows you to copy a function, and fill out some of the arguments before calling it. You can use this to quickly transform existing functions into anonymous functions that can be used as callbacks. In PHP, this is possible with any kind of callable (functions, methods, closures, ...).
<?php $originalFunction = function (string $a, string $b, string $c, string $d): string { return $a . $b . $c . $d; }; // Fix the two first/left-handed arguments. $newFunction = F\apply_l($originalFunction, 'A', 'B'); assert('ABCD' === $newFunction('C', 'D')); // Fix the two last/right-handed arguments. $newFunction = F\apply_r($originalFunction, 'C', 'D'); assert('ABCD' === $newFunction('A', 'B')); // Fix two arguments by index/in the middle. $newFunction = F\apply_i($originalFunction, 1, 'B', 'C'); assert('ABCD' === $newFunction('A', 'D')); ?>
Currying
Currying converts a single function with n parameters to n functions with one parameter each. Practically speaking, it allows you to copy a function, and fill out some of the arguments one at a time before calling it. You can use this to quickly transform existing functions into anonymous functions that can be used as callbacks. In PHP, this is possible with any kind of callable (functions, methods, closures, ...).
<?php $originalFunction = function (string $a, string $b, string $c, string $d = 'D'): string { return $a . $b . $c . $d; }; assert('ABCD' === F\curry($originalFunction)('A')('B')('C')); ?>
Contributing
Your involvement is more than welcome. Please leave feedback in an issue, or submit code improvements through pull requests.
The internet, and this project, is a place for all. We will keep it friendly and productive, as documented in our Code of Conduct, which also includes the project maintainers' contact details in case you want to report a situation, on behalf of yourself or others.
Development
Building the code
Run ./bin/build
.
Testing the code
Run ./bin/test
.
Fixing the code
Run ./bin/fix
to fix what can be fixed automatically.
Code style
All PHP code follows PSR-2.