php-standard-library/result
Result type for PHP that captures success or failure as a value, enabling controlled error handling without exceptions. Part of the PHP Standard Library.
Result::ok($user) / Result::err(ValidationError)) rather than a thrown exception. That is a product-level decision about API contracts and user-visible error behaviour, not just a code-style preference — it lets you promise clients structured failure payloads instead of opaque 500s.Result/Either class. This package is MIT, framework-agnostic, and already defines map, flatMap, match, mapErr, unwrap, unwrapErr, fromCallable. Buying is cheap; the real cost is the migration discipline, not the dependency.processOrder(): Result), queue jobs (handle() branching to $this->fail()), and middleware short-circuits. This gives you a sequencing plan for rollout rather than a big-bang refactor.Result<User, ValidationError> vs. Result<User, ValidationError|PaymentFailed> is a schema decision that gates later observability work — structured error logging, per-error-type dashboards, retry policies on queues, and cleaner client SDKs.assertTrue($result->isFailure())), which reduces flaky edge-case testing on payment, auth, and onboarding flows. This supports a "reduce QA cycle time" objective.Adopt if:
try/catch nesting and inconsistent ad-hoc error arrays, and error shape varies per endpoint.map, flatMap, immutability). Budget a short ramp-up; this is a genuine learning curve, not a drop-in.ResultWrapper bound in the container) so the dependency is reversible.Look elsewhere — or treat as an unproven spike — if:
php-standard-library/php-standard-library). That means independent adoption, issue traffic, and downstream breakage data are all effectively unmeasurable here. Score the parent project, not this package, when assessing maintenance risk.unwrap() on Err.Practical de-risking: pilot on one vertical slice (e.g., a single job + its controller), keep exceptions at the HTTP boundary by converting Result::err() to ValidationException/responses in one place, and track error-shape consistency before and after. If the pilot does not measurably reduce error-handling code or bug count, stop.
For executives: "Today, failures in our Laravel services are scattered — some throw, some return arrays, some silently log. That makes API error responses inconsistent, slows client integration, and makes production issues harder to triage. This package gives us one small, MIT-licensed, dependency-free convention for representing 'this can fail' as a value. It is a three-day pilot on one workflow, behind an internal wrapper so it is fully reversible. The payoff we are targeting is fewer integration questions, faster test cycles on edge cases, and a structured error format we can log and monitor. The cost is a dependency with low visible adoption — which is precisely why we pilot before committing and keep the wrapper."
For engineering: "Result makes expected failures explicit and composable: chain via flatMap, short-circuit naturally, recover with mapErr, and handle once with match. Keep exceptions for genuinely exceptional conditions — DB outages, misconfiguration — and use Result for validation, business rules, and external-call outcomes. Adopt it at the boundaries first: controllers, jobs, middleware, domain services. Do not call unwrap() without a success check. Type your failures (Result<User, ValidationError>), not Result<User, string>. Bind a thin ResultWrapper in the service container so we can swap the implementation without touching call sites. Expect a learning curve of roughly a sprint for anyone unfamiliar with map/flatMap; pair on the first few conversions."
What we will measure in the pilot: lines of error-handling code removed, number of distinct error shapes in API responses before vs. after, time to add a new validated endpoint, and any friction reports from the team. Decision gate at the end of the pilot: standardize, extend to one more workflow, or revert — all three are acceptable outcomes at this cost.
How can I help you explore Laravel packages today?