Incremental Migration
You don't need a rewrite to adopt Maybe. Each block stands on its own, and they compose as you go.
Recommended order
- Schema at the boundaries — replace ad-hoc
isset/emptyvalidation with a schema at each input boundary (HTTP request, CSV import, queue payload). Nothing else changes yet. - DTOs for the messy inputs — where validated data crosses layers, wrap it in a DTO so the shape is guaranteed downstream.
- Result in services — new/changed service methods return
Resultinstead of throwing or returningfalse/null. Callersmatchat the edge. - Option for nullable lookups — repositories return
Optioninstead ofnull, converted withokOr()when a typed error is needed. - Async last — only after the team is comfortable, and only for isolated, serializable workloads.
Coexisting with exceptions
Maybe does not force an all-or-nothing style. At boundaries with exception-based code:
php
use Maybe\Result\Result;
function tryCatch(callable $fn): Result
{
try {
return Result::ok($fn());
} catch (\Throwable $e) {
return Result::err($e);
}
}And in the opposite direction, unwrap()/expect() convert an Err back into an exception at the layer where exceptions are the convention.
What to avoid
- Don't wrap everything in Option/Result on day one — start where errors actually hurt.
- Don't pass
Resultdeep into layers that never inspect it; resolve it at the closest sensible boundary. - Don't use
unwrap()as a shortcut in production paths — prefermatch,unwrapOr()orexpect()with a meaningful message.
For deeper guidance, see the repository docs: usage patterns, practical recipes and anti-patterns.