14 May 2026 · 2 min read
Monads in PHP: A Gentle Introduction with Phunkie
- functional-programming
- php
- phunkie
Every monad tutorial starts with an apology, so here is mine: this one contains no burritos, no boxes, and no claims that you need a PhD to follow along. A monad is a design pattern for sequencing computations that carry context — and you have almost certainly written one by accident.
The problem monads solve
Consider the classic null-check pyramid:
$user = $repository->find($id);
if ($user !== null) {
$address = $user->getAddress();
if ($address !== null) {
$postcode = $address->getPostcode();
if ($postcode !== null) {
return Postcode::normalise($postcode);
}
}
}
return null;Every step can fail, and every step must remember that the previous one could have failed too. The context (this value might be absent) leaks into every line of the business logic.
Sure, nowadays you can use the nullsafe operator:
$postcode = $repository->find($id)?->getAddress()?->getPostcode();But that only flattens this particular kind of sequence, the one where every step might be null. The idea we are about to look at extends to other types of computation, not just this one.
Option: making absence a value
Phunkie's Option type turns "might be absent" into a first-class
value with two cases: Some($value) and None.
$result = Option($repository->find($id))
->flatMap(fn($user) => Option($user->getAddress()))
->flatMap(fn($address) => Option($address->getPostcode()))
->map(fn($postcode) => Postcode::normalise($postcode));The pyramid is gone. Each step describes one transformation, and the
sequencing logic — "stop if anything was missing" — lives in flatMap,
written once, tested once, reused forever.
And if you make your functions return Option in the first place, the
code simplifies a lot:
$result = $repository->find($id)
->flatMap(fn($user) => $user->getAddress())
->flatMap(fn($address) => $address->getPostcode())
->map(fn($postcode) => Postcode::normalise($postcode));So what makes it a monad?
Three things, and you've already seen two of them:
- A constructor that lifts a plain value into the context —
Option($x)gives you aSomeorNone. - A
flatMap(sometimes calledbind) that sequences a computation which itself returns the context. - A set of laws guaranteeing that sequencing behaves predictably — no surprises when you refactor.
That's the whole trick. ImmList is a monad where the context is
"many values". Validation is a monad where the context is "may have
failed with an error worth keeping". The pattern is identical; only
the context changes.
Why bother in PHP?
Because the pyramid of doom never went away — it just moved into your service classes. Once you name the pattern, you stop re-implementing it badly in every method that touches a nullable value.
If this whetted your appetite, Phunkie
ships Option, ImmList, Validation, and friends — and the
Introduction to Functional Programming with Phunkie course (coming
soon) walks through all of them at a humane pace.