graham-campbell/result-type
Result Type is a PHP implementation of the result type, maintained by Graham Campbell. It offers a clean way to represent success or failure values instead of exceptions. Install via Composer with graham-campbell/result-type ^1.1; requires PHP 7.2.5-8.6. MIT licensed.
Architecture fit. graham-campbell/result-type is a framework-agnostic value object providing a Result base type with Success (value) and Error (reason) variants. Its value to a Laravel organization is architectural rather than functional: it converts failure from a thrown side effect into a returned value, which is valuable precisely where failure is an expected business outcome rather than an exceptional one — payment declines, third-party API rejections, parsing/validation of external payloads, multi-step workflows with partial success. It is explicitly not a replacement for Laravel's exception-driven core (HTTP kernel, FormRequest validation, middleware, queue retries, Eloquent). The defensible position for stakeholders is a complementary pattern confined to the service/domain layer, with translation adapters at the edge. If the team cannot state and enforce that boundary in a single paragraph, the package will introduce a second competing error-handling paradigm and reduce clarity rather than improve it.
Integration feasibility. Installation cost is effectively zero — composer require graham-campbell/result-type:^1.1, no service provider, no config publishing, no migrations, no runtime hooks, no framework coupling to track across Laravel upgrades. The real cost is convention, adapter code, and refactoring effort:
Result in those layers requires translation shims, which must be written before the first Result ships.Success/Error hierarchy (@return Result<int, ErrorCode>) to be useful; without a configured rule or lint gate, unwrapped Error branches will slip through review.Technical risk. Moderate, and predominantly organizational rather than technical:
new Success(1) === new Success(1) is false), and destructuring. A carelessly unwrapped Error becomes a runtime failure — the exact failure mode the pattern exists to prevent. This is a discipline risk, not a package defect, and it is the single most likely cause of a failed pilot.Result within one call stack produces confusing stack traces and inconsistent error contracts across teams. Every additional team that adopts with a different convention compounds this.phpoption ≥1.10. It is a small, same-author transitive surface — pin and audit the lock file rather than treating it as zero-dependency.Key questions to resolve before approval.
Error taxonomy — string codes, backed enums, or typed error objects — and who owns its evolution?Result conversion live for third-party SDKs that throw? Who reviews that adapter, and is it tested against both branches?Result values? Without a mechanical gate, the convention will erode within two sprints.TPM recommendation. Conditional buy for a single bounded pilot: one domain, one named owner, one quarter, with a written convention document and a go/no-go review at the end. Reject "adopt everywhere" outright.
Stack fit. Compatible with any Composer-based PHP stack on PHP 7.2.5–8.6, including current Laravel releases. It has no framework integration points by design — a strength (no coupling, no upgrade drag, no Laravel version matrix to maintain) and simultaneously a weakness (no guardrails, no container bindings, no opinion about where it belongs). It fits service classes, domain objects, HTTP client wrappers, and console command handlers. It does not fit controller return types, Eloquent model attributes, middleware signatures, or job payloads without bespoke adapters.
Migration path. Do not migrate existing exception-based code. Use a strangler-style, boundary-first rollout:
Result only inside that service; its signature (Result<T, E>) becomes the explicit contract.Result → HTTP response in the controller, and Result → domain exception where the framework insists on one.Result is internal to the service layer; exceptions remain the language of the framework edge.Compatibility considerations.
^1.1 and verify the resolved phpoption version in the lock file, particularly if a PHP 8.4/8.5/8.6 upgrade is in flight.Result across queues, caches, or API responses — convert to a plain DTO, enum, or exception at the boundary. This is the most common design mistake with these types.Sequencing.
composer require, spike one service, draft the convention one-pager with adapter examples, confirm CI and static analysis behave. Deliverable: a decision-ready prototype, not production code.Success and Error branches, not just happy paths. Add the static-analysis rule or a lint checklist to CI in the same pull request.Maintenance. External maintenance burden is close to zero: small, stable, permissively licensed, and prompt on PHP releases. The ongoing cost is internal convention upkeep — error taxonomy, adapter code, static-analysis configuration, and reviewer discipline. Assign a named tech lead as pattern owner; without one, usage will drift into per-team variants within a quarter and the pattern's value will invert.
Support. Community support through GitHub with a documented security disclosure process; commercial support via Tidelift if procurement requires an SLA or indemnity. Given the size of the codebase, a fork-and-vendor contingency is realistic and inexpensive. Record both the support model and the fork trigger conditions in the risk register — do not assume enterprise-grade backing unless the Tidelift subscription is actually purchased.
Scaling. Runtime overhead is negligible and will not register against database or network latency; this is not a performance decision. The scaling risk is cognitive. The pattern scales well when confined to a declared boundary and poorly when it spreads opportunistically — inconsistent Result usage becomes a permanent code-review tax that grows with headcount. Mitigate with a one-page internal standard, in-repo reference examples, and a reference implementation the team can copy.
Failure modes. Rank the risks the team should plan for:
Result reaches a controller, job, or JSON response and consumers mishandle it. Detection: code review plus a static-analysis rule; blast radius is a runtime error at the worst possible moment.Error branch — the pattern silently degrades into a verbose false. Detection: require both-branch test coverage and enable a static-analysis rule that flags discarded results.Error values are untranslatable across services. Detection: single owned enum/type per bounded context, reviewed at the architecture forum.Result call stacks produce unreadable traces and inconsistent API error contracts. Detection: adapter code review and a documented translation table from Error to HTTP status.Ramp-up. Low in absolute terms — the API is small and a competent Laravel developer can learn it in a day. The expensive part is the discipline, not the syntax: developers must internalize when to return a Result versus throw, how to write both adapters, and how to test the Error path. Budget roughly half a day of onboarding per developer plus one to two weeks of pair-reviewed pilot work for the first team, and expect code-review friction to spike briefly before settling. Do not onboard a second team until the first team's convention document has been validated by real code.
How can I help you explore Laravel packages today?