Skip to content

Repository files navigation

Functional PHP

PHP Functional Programming library. Monads and common use functions.

psalm levelpsalm type coveragephpunit coverage

Documentation

Installation

Composer

Supported installation method is via composer:

$ composer require fp4php/functional

Psalm integration

Please refer to the fp4php/functional-psalm-plugin repository.

Overview

Typesafe and concise.

Powerful combination: Collections + Option monad.

<?phpuseFp\Collections\ArrayList;
useFp\Functional\Option\Option;
usefunctionFp\Evidence\of;
usefunctionFp\Evidence\proveString;
class PgSqlCurrencyArrayType extends Type
{
publicfunctionconvertToDatabaseValue($value, AbstractPlatform$platform): string
{
$currencies = Option::fromNullable($value)
->filter(is_iterable(...))
->getOrElse([]);
return ArrayList::collect($currencies)
->flatMap(of(Currency::class))
->map(fn(Currency$currency) => $currency->getCurrencyCode())
->mkString('{', ',', '}');
}
/** * @return ArrayList<Currency> */publicfunctionconvertToPHPValue($value, AbstractPlatform$platform): ArrayList
{
$csv = Option::fromNullable($value)
->flatMap(proveString(...))
->map(fn(string$pgSqlArray) => trim($pgSqlArray, '{}'))
->getOrElse('');
return ArrayList::collect(explode(',', $csv))
->filterMap($this->parseCurrency(...));
}
/** * @return Option<Currency> */publicfunctionparseCurrency(string$currencyCode): Option
{
return Option::try(fn() => Currency::of($currencyCode));
}
}

Examples

  • Type safety
<?phpuseFp\Collections\NonEmptyLinkedList;
/** * Inferred type is NonEmptyLinkedList<1|2|3> */$collection = NonEmptyLinkedList::collectNonEmpty([1, 2, 3]);
/** * Inferred type is NonEmptyLinkedList<int> *  * Literal types are dropped after map transformation, * but NonEmpty collection prefix has been kept */$mappedCollection = $collection->map(fn($elem) => $elem - 1);
/** * Inferred type is LinkedList<positive-int> * NonEmpty prefix has been dropped */$filteredCollection = $mappedCollection->filter(fn(int$elem) => $elem > 0);
<?phpuseTests\Mock\Foo;
useTests\Mock\Bar;
useFp\Collections\NonEmptyArrayList;
$source = [newFoo(1), null, newBar(2)];
/** * Inferred type is ArrayList<Foo|Bar> * Null type was removed * NonEmpty prefix was removed */$withoutNulls = NonEmptyArrayList::collectNonEmpty($source)
->filter(fn(Foo|Bar|null$elem) => null !== $elem);
/** * Inferred type is ArrayList<Foo> * Bar type was removed */$onlyFoos = $withoutNulls->filter(fn($elem) => $eleminstanceof Foo);
  • Covariance
<?phpuseFp\Collections\NonEmptyLinkedList;
class User {}
class Admin extends User {}
/*** @param NonEmptyLinkedList<User> $collection*/functionacceptUsers(NonEmptyLinkedList$collection): void {}
/**  * @var NonEmptyLinkedList<Admin> $collection  */$collection = NonEmptyLinkedList::collectNonEmpty([newAdmin()]);
/** * You can pass collection of admins instead of users * Because of covariant template parameter */acceptUsers($collection);
  • Immutability
<?phpuseFp\Collections\LinkedList;
$originalCollection = LinkedList::collect([1, 2, 3]);
/** * $originalCollection won't be changed */$prependedCollection = $originalCollection->prepended(0);
/** * $prependedCollection won't be changed */$mappedCollection = $prependedCollection->map(fn(int$elem) => $elem + 1);
  • Null safety
<?phpuseFp\Functional\Option\Option;
useFp\Collections\ArrayList;
/** * @var ArrayList<int> $collection  */$collection = getCollection();
/** * @return Option<float> */functiondiv(int$a, int$b): Option
{
return Option::when(0 !== $b, fn() => $a / $b);
}
/** * It's possible there is no first collection element above zero * or divisor is zero. * * In this case the execution will short circuit (stop) * and no Null Pointer Exception will be thrown. */$collection
->first(fn(int$elem) => $elem > 0)
->map(fn(int$elem) => $elem + 1)
->flatMap(fn(int$elem) => div($elem, $elem - 1))
->getOrElse(0)
<?phpuseTests\Mock\Foo;
useFp\Functional\Option\Option;
usefunctionFp\Evidence\proveTrue;
usefunctionFp\Evidence\proveNonEmptyList;
/** * Inferred type is Option<Foo>  */$maybeFooMaybeNot = Option::do(function() use ($untrusted) {
// If $untrusted is not null then bind this value to $notNull$notNull = yield Option::fromNullable($untrusted);
// If $notNull is non-empty-list<Tests\Mock\Foo> then bind this value to $nonEmptyListOfFoo $nonEmptyList = yieldproveNonEmptyList($notNull, of(Foo::class));
// Continue computation if $nonEmptyList contains only one elementyieldproveTrue(1 === count($nonEmptyList));
// I'm sure it's Foo objectreturn$nonEmptyList[0];
});
// Inferred type is Tests\Mock\Foo$foo = $maybeFooMaybeNot->getOrCall(fn() => newFoo(0));

Contribution

Build documentation

  1. Install dependencies
$ sudo apt install pandoc
  1. Generate doc from src
$ make

About

PHP Functional Programming library. Monads, common use functions and generic collections.

Topics

Resources

Stars

203 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages