Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Functional Laravel Package

fp4php/functional

fp4php/functional brings functional programming to PHP with immutable collections, pipelines, pattern matching, and composable helpers. It simplifies data transformation and reduces boilerplate while fitting naturally into Laravel and existing PHP codebases.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require fp4php/functional

There is no service provider and no config file — this is a zero-bootstrap library. Everything is plain PHP classes you import directly.

Start with Fp\Functional\Option\Option, because that's the piece Laravel is missing most. The canonical first use case is replacing a nullable return from a repository or Eloquent lookup:

use Fp\Functional\Option\Option;

final class UserRepository
{
    public function findById(int $id): Option
    {
        return Option::fromNullable(User::find($id));
        // Some(User) or None — the null is now part of the type
    }
}

// Caller
return $this->repo->findById($id)
    ->map(fn (User $u) => new UserResource($u))
    ->map(fn (UserResource $r) => response()->json($r))
    ->getOrElse(response()->json(['error' => 'Not found'], 404));

Where to look first, in order:

  1. src/Fp/Functional/Option/* — Option, Some, None. Read the map/flatMap/filter/match signatures; they're the whole surface area.
  2. src/Fp/Functional/Either/* — Left/Right, for error handling without exceptions.
  3. src/Fp/Collection/Collection — the immutable, lazy collection. Only reach for this after you've decided Laravel's Collection isn't enough.

The second first use case is Either in an action class that has two meaningful failure modes (validation vs. not-found vs. conflict) where you don't want to throw:

use Fp\Functional\Either\Either;

$result = $this->action->execute($dto); // Either

return $result->match(
    fn ($ok)  => response()->json($ok, 200),
    fn ($err) => response()->json(['message' => $err->message], $err->status),
);

Implementation Patterns

Boundary discipline. Monads leak badly if you half-adopt them. Pick a convention: domain/service/action layers return Option/Either, and controllers unwrap them with match, getOrElse, or getOrThrow at the edge. Never let an Option reach a Blade view or a JSON response payload.

Repository → Option. Wrap every first()/find() that can miss. Chain ->map() for transformations and ->flatMap() when the next step can also return Option (the classic flatMap-vs-map mistake is nesting Option<Option<T>>).

Either for domain errors. Define small error value objects (NotFound, Conflict, InvalidTransition) and return Either::left(new NotFound()). This keeps exception handling for genuinely exceptional things and makes the error path explicit in the signature. Since fp4php won't catch for you, wrap third-party throws yourself:

$result = (function () use ($input) {
    try {
        return Either::right($this->riskyService->call($input));
    } catch (DomainException $e) {
        return Either::left(new Failed($e->getMessage()));
    }
})();

Validated for accumulating errors. If you need all validation failures at once (not just the first), Validated::valid() / Validated::invalid() and combining instances is the reason this library exists over Either. In Laravel you'd typically still use the Validator facade for HTTP forms and reserve Validated for domain-level checks (e.g., multi-line import rows) where Laravel's validator doesn't fit.

Collection pipelines for pure transformations. Collection::of($iterable)->map(...)->filter(...)->groupBy(...) is nice for report/aggregation code where laziness and immutability matter. Convert back out at the end with whatever to-array/list method the version exposes, and remember Laravel Collection itself is iterable, so it can be fed in directly.

Interop, not replacement. Option::fromNullable(), Collection::of($laravelCollection), and extracting with ->getOrElse() are the three functions that make the library coexist with Eloquent and collect(). Use them as adapters at the seams.

Static analysis. The main payoff over hand-rolled null checks is generics annotations. Run PHPStan or Psalm at a decent level — the @template annotations on Option/Either/Collection are what let the analyzer actually track types through map/flatMap. Without a static analyzer you lose half the value.

Gotchas and Tips

  • Name collision. Fp\Collection\Collection vs Illuminate\Support\Collection is the #1 source of use statement confusion. Alias one: use Fp\Collection\Collection as FpCollection;.
  • Not a drop-in for collect(). The API overlaps but is not identical, and it's lazy and immutable. ->map() returns a new object; there's no ->push(). If you have existing Collection code, don't do a blind find-and-replace.
  • Option::some(null) is legal and you don't want it. Use Option::fromNullable() when the value might be null; some() is for "I already know this is non-null." Mixing them up produces Some(null) that sails straight through ->map().
  • Don't === compare monads. Some(1) !== Some(1). Compare via ->isSome()/->isNone()/->isLeft()/->isRight(), or unwrap first and compare the raw values.
  • Debugging is painful. dd($option) dumps nested wrapper objects. Use ->match(fn ($v) => dd(['some', $v]), fn () => dd('none')) or ->getOrElse('NONE') to see what you actually have.
  • Perf. Each map/flatMap allocates. For tight loops over large arrays, plain array_map or a single Laravel Collection pass will be measurably faster. Reserve fp4php for orchestration-level code, not inner loops.
  • No exception catching in the monads. Either and Option model values, not throwables. Any throw inside a map callback escapes the pipeline entirely.
  • Small ecosystem (203 stars). Fewer Stack Overflow answers and third-party tutorials; when something is off, read the source (it's small) and pin your version. Check the changelog when bumping — pre-1.0-ish libraries in this space rename static constructors between minor releases.
  • Extension points are the Semigroup/Monoid interfaces and the Option/Either interfaces themselves. Implement Semigroup when you want repeated combine-style aggregation of a domain type; implement your own thin monad only if you genuinely need one — otherwise compose Option and Either and stop there.
  • Resist total adoption. Laravel's framework layer (HTTP, validation, Eloquent) is idiomatically exception- and null-based. Keeping monads in services/actions and converting at the controller boundary is the pattern that survives code review; sprinkling Option through models, Blade, and jobs is the pattern that gets reverted.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core