php-standard-library/option
Option type for PHP with explicit Some/None presence semantics, replacing nullable values so absence is handled intentionally instead of silently. Part of the PHP Standard Library.
Architecture Fit
Option is a boundary type, not an ambient one. In a Laravel codebase it fits cleanly in one place — the seam between infrastructure (Eloquent, HTTP, config, third-party APIs) and your domain/application layer — and poorly everywhere else. Concretely:
Option<User> instead of ?User), action/service outputs, DTO construction from request payloads, and external API client responses where "absent" and "failed" must be distinguished.config(), and anything that crosses a JSON boundary. Laravel's idioms, its helpers, and its serialization layer all speak null; Option does not serialize, and Eloquent's magic property access cannot express the difference between "relationship not loaded" and "relationship absent."?T → Option<T> migration is not.Integration Feasibility
Installation is trivial — a framework-agnostic Composer package with zero Laravel coupling, no service provider required, and per-file adoptability. The real feasibility question is not install but type-system feasibility: PHP generics are docblock-only, so Option<User> is enforced by PHPStan/Psalm, not by the compiler. Your static analysis configuration becomes load-bearing infrastructure. If the team's PHPStan level is low or unenforced in CI, the package's primary value proposition — type safety — does not materialize.
Signal Quality Caveat
The package shows 1 star and 0 dependents, but it is a satellite of php-standard-library/php-standard-library; releases are cut in the monorepo and the package README defers all documentation upstream. Read the low numbers as a packaging artifact, not as adoption data — but do read them as evidence that this repo has no independent changelog, issue tracker, or release cadence. Your dependency is really on the parent project's health, and the provided release notes ("See the main repository release for details") give you nothing to diff against.
Technical Risk
Option.Option::from(null) does. If Some(null) is constructible, you have reintroduced null inside the wrapper and lost the entire benefit.Option, legacy returns ?T, callers guess wrong.?->, and mistakes (unwrapping eagerly, swallowing None where an exception was warranted) are subtle.Key Questions to Resolve Before Commit
fromNullable/toNullable adapters, and does from(null) yield None or throw?null ambiguity a measured problem here (bug counts, incident reviews), or an aesthetic one?Stack Fit
Pure PHP, Composer-installable, no framework hooks, no global state, no config file. It coexists with everything and therefore conflicts with nothing except convention. The binding constraint is not technical compatibility but team agreement on where the null/Option boundary sits.
Recommended Migration Path (Staged, Reversible)
map/filter, unwrap at the controller. Success criteria defined up front: PHPStan errors introduced, review time delta, lines removed vs. isset() baseline, and developer sentiment from the two people who did it.Option permitted in domain/application/service signatures and DTO fields; forbidden in Eloquent attributes, Blade, config, and controller return values. Ship internal adapter helpers and a PHPStan baseline so the rules are mechanically enforced rather than socially enforced.Compatibility Notes
Option must be unwrapped before leaving a controller. Add this to the review checklist — a leaked Option in a JSON response is a silent API contract break.Option outside model attributes; convert at the repository boundary only.nullable rule; convert after validation, so validation errors stay idiomatic.Sequencing Rationale
Adapters before adoption, tooling before mandate. If you mandate Option before the PHPStan rules exist, you get inconsistent usage that is expensive to unwind; if you adopt before adapters exist, framework boundaries become a manual unwrap discipline that decays within a quarter.
Maintenance
Code surface is small and the dependency tree shallow, so runtime maintenance is negligible. The real maintenance load is policy maintenance: the convention doc, the PHPStan config, the adapter helpers, and the upgrade path on major bumps. That is a recurring, owned responsibility — assign it to a named engineer, not "the team." Upstream risk is moderate: the parent project is active, but this satellite repo provides no independent changelog, so each 6.x bump requires reading the monorepo release notes to infer impact.
Support and Ramp-Up
Most Laravel developers have not used a monadic optional type. Expect 2–4 weeks to productive fluency for a mid-level engineer, and longer for reviewers, whose job gets harder before it gets easier. Budget for a short internal workshop, a cheat sheet (map vs flatMap vs unwrap), and a review checklist. Ongoing support burden does not disappear — it shifts from debugging null propagation to debugging Option chain semantics, which is a different and initially rarer skill.
Scaling
Runtime cost is negligible: one small object allocation per Some, no reflection, no container work. Not a concern for typical request/response workloads. The only place to check is large in-memory collection transforms, where wrapping every element doubles allocation count — measure before adopting inside hot Collection::map loops over thousands of rows.
Failure Modes to Instrument
Some(null) — silently reintroduces the ambiguity the package exists to remove.unwrap — converts a None into a thrown exception, trading a null-check for a crash site, often further from the root cause.None swallowing genuine errors — collapsing "not found" and "lookup failed" into the same value. Keep exceptions for errors; reserve None for legitimate absence.?T on an Option-returning method, or vice versa, during transition.Option reaching a JSON response or a Blade view.Metrics for the Pilot Gate
Null-related production incidents, PHPStan error volume, PR review turnaround on touched modules, and count of isset/??/?-> per file in refactored areas.
TPM Verdict
Approve a timeboxed, single-module pilot with a hard decision gate. Do not approve a codebase-wide migration. The package is technically sound and cheap to install, but its value is contingent on static-analysis discipline and team willingness to learn a new idiom — neither of which the package supplies. The strongest argument for the pilot is that it is reversible in a week; the strongest argument against broader adoption is that Laravel's ecosystem convention is null, and Option is a permanent friction cost against every framework helper, every Eloquent call, and every new hire. Adopt it where ambiguity measurably hurts. Skip it where it does not.
How can I help you explore Laravel packages today?