PHP Functional Programming library. Monads and common use functions.
Supported installation method is via composer:
$ composer require fp4php/functionalPlease refer to the fp4php/functional-psalm-plugin repository.
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));
}
}- 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));- Install dependencies
$ sudo apt install pandoc- Generate doc from src
$ make