widmogrod/php-functional
Functional programming toolkit for PHP with immutable data structures, monads and typed abstractions. Includes Option/Either/Try, collections, pattern matching, and helpers for safer, composable code. Useful for DDD, CQRS and FP-style application architecture.
Install via Composer: composer require widmogrod/php-functional. Start with the foundational types: Option for nullable values (avoiding null checks) and Either for explicit error handling. For example, replace a fragile getUserById($id) that might return null with Option::fromNullable($user) and safely chain operations: Option::fromNullable($user)->map(fn($u) => $u->getEmail())->getOrElse('no-email@example.com'). For error-prone operations (e.g., parsing, external API calls), prefer Either over exceptions in business logic:
$result = Either::tryCatch(fn() => json_decode($input, true))
->mapLeft(fn($e) => "Invalid JSON: " . $e->getMessage());
Check the src/ folder for core classes (Option, Either, Collection) and use PSR-18/HTTP client integrations if present.
Collection::from($items)->map()->filter()->reduce() for domain-wide data processing (e.g., transforming order payloads without intermediate arrays).Either to model validation failures in DTOs or use cases—e.g., validateUserInput($data)->flatMap(fn($input) => createUser($input)) where validateUserInput() returns Either<Error, ValidatedInput>.Either::tryCatch() at the edge, then compose pure functions upstream.match or matchWith: Replace nested if/else or switch with declarative branching:Option::just($user)->match(
fn($u) => "User: {$u->getName()}",
fn() => "Guest"
);
isEligible::class) into services instead of hardcoding logic, enabling easier unit testing.Option, Either, Collection) are immutable—calls like map() return new instances. Accidentally reusing variables leads to stale values; use strict typing (declare(strict_types=1)) and IDE support to catch this.Either<Error, Either<Error, T>>. Prefer flattening with flatMap or introduce small domain-specific helpers (e.g., ValidationResult::success($data)).Collection uses lazy evaluation internally; avoid toArray() in tight loops—use each() or reduce() for streaming.Either; reserve it for expected errors (validation, domain rules). Reserve exceptions for unexpected failures (database down, programming bugs).Option/Either values, not instances. Use ->getOrThrow() only in tests for clarity, not production code.Validation or Result wrapper types by extending Either and implementing domain-specific combinators (e.g., andThenValidateEmail()).peek() in chains to log intermediate values without breaking composition: Collection::from($items)->map(...)->peek(fn($x) => logger()->info('Got item', $x))->reduce(...).How can I help you explore Laravel packages today?