Pre-Draft: Standard Interfaces for Recoverable Errors and Operation Results

53 views
Skip to first unread message

Yousha Aleayoub

unread,
Dec 14, 2025, 1:34:51 PM (5 days ago) Dec 14
to PHP Framework Interoperability Group
Hello,

I’m submitting for discussion and vote a new PSR proposal to address a long-standing gap in PHP’s error-handling ecosystem:

Standard Interfaces for Recoverable Errors and Operation Results
(Working title: PSR-XX Error and Result Interfaces)

The problem
PHP forces developers to choose between two flawed patterns for non-exceptional failures:

  • Throwing exceptions for expected outcomes (like validation errors, cache misses) -> breaks control flow, hurts performance, and conflates recoverable vs. unrecoverable errors.
  • Returning ambiguous values (null, false, ['error' => ...]) → destroys type safety and forces brittle checks across the call stack.
Frameworks and libraries reinvent this wheel constantly:
  • Laravel’s ValidationException
  • Symfony’s ConstraintViolationList
  • Guzzle’s TransferException + status codes
  • Custom Result<bool, string> classes in libraries...
There is no interoperable, type-safe way to return structured, recoverable errors.

The Solution
This PSR will propose three minimal, zero-dependency interfaces:

  1. ResultInterface: a sealed union for success/failure states
  2. SuccessInterface: wraps a successful value
  3. FailureInterface: wraps structured error data (code, message, context) and optionally a Throwable
Key features
  • No exceptions required for expected failures!
  • Immutable, readonly objects
  • Pattern-matchable via isSuccess()/isFailure()
  • Composable with PSR-3 (logging), PSR-11 (containers), and async runtimes
  • Zero runtime overhead – pure interfaces, no DI or middleware
Thanks for your time.

Larry Garfield

unread,
Dec 15, 2025, 5:29:58 PM (4 days ago) Dec 15
to PHP-FIG
On Sun, Dec 14, 2025, at 12:02 PM, Yousha Aleayoub wrote:
> Hello,
>
> I’m submitting for discussion and vote a new PSR proposal to address a
> long-standing gap in PHP’s *error-handling* ecosystem:
>
> Standard Interfaces for Recoverable Errors and Operation Results
> *(Working title: PSR-XX Error and Result Interfaces)*
> *
> *
> *The problem*
> PHP *forces *developers to choose between two flawed patterns for
> non-exceptional failures:
>
> • Throwing exceptions for expected outcomes (like validation errors,
> cache misses) -> breaks control flow, hurts performance, and conflates
> recoverable vs. unrecoverable errors.
> • Returning ambiguous values (null, false, ['error' => ...]) →
> destroys type safety and forces brittle checks across the call stack.
> Frameworks and libraries reinvent this wheel constantly:
> • Laravel’s ValidationException
> • Symfony’s ConstraintViolationList
> • Guzzle’s TransferException + status codes
> • Custom Result<bool, string> classes in libraries...
> There is no interoperable, type-safe way to return structured,
> recoverable errors.
>
> *The Solution*
> This PSR will propose three minimal, zero-dependency interfaces:
>
> 1. ResultInterface: a sealed union for success/failure states
> 2. SuccessInterface: wraps a successful value
> 3. FailureInterface: wraps structured error data (code, message,
> context) and optionally a Throwable
> *Key features*
> • No exceptions required for expected failures!
> • Immutable, readonly objects
> • Pattern-matchable via isSuccess()/isFailure()
> • Composable with PSR-3 (logging), PSR-11 (containers), *and async
> runtimes*
> • Zero runtime overhead – pure interfaces, no DI or middleware
> Thanks for your time.

Hi Yousha.

What you're describing is a user-space Result type. That's been discussed a few times throughout the years in PHP.

Worth reading on this topic:

https://www.garfieldtech.com/blog/much-ado-about-null

The main problem is that, since PHP lacks generics, using an abstracted "result" or "failure" interface means you lose type information for what the success value is. It could be somewhat emulated in PHPStan docblocks, but FIG generally tries to avoid relying on that as a solution. That's one reason you see so many one-off list types and validation error types: It would be great to not need that, but without generics, we kinda do need that.

Also, there is runtime overhead. The actual result objects that wrap the underlying value would add, at least, a method call for extracting the success value.

I'm not sure this is a great thing to standardize and push without generics. Had we those, I'd be all over this. :-)

For now, the "naked Either" approach described in the blog above is the least-bad solution I have found.

--Larry Garfield

Korvin Szanto

unread,
Dec 16, 2025, 3:00:42 PM (3 days ago) Dec 16
to php...@googlegroups.com
--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/php-fig/ed4abd14-7f0c-4559-988c-8afd04538be3n%40googlegroups.com.

Thanks for putting this and the PR together. I definitely think this would be a good thing to have, but I agree with Larry that the lack of generics makes this challenging.

The solution you provide in this thread is different than whats presented in https://github.com/php-fig/fig-standards/pull/1344 . Can you clarify what you intend to do to avoid the mixed typing for both success and failure? Are you thinking an implementor would provide a different result class for each possible result?

Thanks,
Korvin

Yousha Aleayoub

unread,
Dec 17, 2025, 3:15:37 AM (3 days ago) Dec 17
to PHP Framework Interoperability Group
@Larry Garfield, about lack of generics in PHP you are right, but my proposal is NOT trying to solve generics, it’s trying to solve "fragmentation"...

Right now, every library reinvents its own Result/ErrorBag. Even if imperfect, a shared interface (like PSR‑3) would reduce duplication and improve interoperability.

Also runtime overhead is minimal compared to the clarity gained(specially with current hardware and I/O). I see this as a "baseline contract" rather than a perfect solution; similar to how PSR‑3 gave us a common logger interface "without solving structured logging"


@Korvin Szanto,
Thanks,  AFAIK PHP-FIG already standardizes APIs that rely on phpdoc typing and runtime contracts. (mostly)
This PSR follows the same precedent. Like: 
In PSR-7 we have ServerRequestInterface::getParsedBody(): mixed
In PSR-18 we have sendRequest() that returning interface without generics
etc...

So since PHP lacks native generics, so this new PSR follows existing FIG practice: phpdoc generics plus runtime invariants.
A Result is either success or failure by construction; mixed typing is not allowed.
Implementors are not expected to create a class per result—one implementation per library is sufficient.
I’ll align the META to make this explicit and consistent with the PR.

Korvin Szanto

unread,
Dec 17, 2025, 11:49:58 AM (2 days ago) Dec 17
to php...@googlegroups.com
On Wed, Dec 17, 2025 at 12:15 AM Yousha Aleayoub <y.ale...@gmail.com> wrote:
@Larry Garfield, about lack of generics in PHP you are right, but my proposal is NOT trying to solve generics, it’s trying to solve "fragmentation"...

Right now, every library reinvents its own Result/ErrorBag. Even if imperfect, a shared interface (like PSR‑3) would reduce duplication and improve interoperability.

I'm not sure I can see a clear path for this PSR as written to be used by the libraries you mentioned, but that can be sorted out in draft phase.

 
Also runtime overhead is minimal compared to the clarity gained(specially with current hardware and I/O). I see this as a "baseline contract" rather than a perfect solution; similar to how PSR‑3 gave us a common logger interface "without solving structured logging"

That's fair, I think Larry is just responding to your "Zero runtime overhead" claim. 

 

@Korvin Szanto,
Thanks,  AFAIK PHP-FIG already standardizes APIs that rely on phpdoc typing and runtime contracts. (mostly)
This PSR follows the same precedent. Like: 
In PSR-7 we have ServerRequestInterface::getParsedBody(): mixed

PSR-7 was created at a time when PHP had no return types. If it were being created today `getParsedBody` would certainly return `null|array|object`, though I do get your point that this interface existed in a time where typing wasn't available and it relied on docblocks to narrow return types.
 
In PSR-18 we have sendRequest() that returning interface without generics

Return types can be narrowed to a subtype without violating LSP. So an implementor could implement `public function sendRequest(): SpecificResponse` as long as `SpecificResponse` implements `ResponseInterface`. That's quite different from what we're talking about here unless you're expecting an implementor to add many different Result types.
 
etc...

So since PHP lacks native generics, so this new PSR follows existing FIG practice: phpdoc generics plus runtime invariants.
A Result is either success or failure by construction; mixed typing is not allowed.
Implementors are not expected to create a class per result—one implementation per library is sufficient.
I’ll align the META to make this explicit and consistent with the PR.

So to be clear the expectation is that a user MUST add a docblock with `@return ResultInterface<SuccessTypes, ErrorTypes>`, correct? I think the challenge with this today is the ambiguity around how `@template` works and the potential for future changes to make the generics we define today in docblocks overly cumbersome or wholesale unusable. In comparison, PSR-7 didn't face any risk that community libraries would change how `Foo|Baz|null` was interpreted. 

 
To view this discussion visit https://groups.google.com/d/msgid/php-fig/2f7364d1-1c8d-48c0-b8f3-6ee792db2ae3n%40googlegroups.com.



That all said, I think there's enough here that this could pass an entrance vote and some of the questions around generics and enforcing invariance can be answered in the draft phase over time. If we're lucky, usable generics will land in PHP before this PSR is finalized.
 

Larry Garfield

unread,
Dec 17, 2025, 12:28:28 PM (2 days ago) Dec 17
to PHP-FIG
On Wed, Dec 17, 2025, at 10:49 AM, Korvin Szanto wrote:

> That all said, I think there's enough here that this could pass an
> entrance vote and some of the questions around generics and enforcing
> invariance can be answered in the draft phase over time. If we're
> lucky, usable generics will land in PHP before this PSR is finalized.

I would not bet on any such luck... :-)

I am still not convinced that interfaces are even the right solution here. If we want to have a standard result type in PHP... it should be a full on type: A concrete class. Which means probably coming from a PER, not a PSR.

That said, I am still not convinced that it's meaningfully useful without native generics. PHPStan/Psalm generics are *fairly* well supported these days, but not universally. And if we did something without generics, then PHP does somehow get generics... upgrading will be a mess.

And of course, I must note that Ilija and I are still trying to work toward ADTs, which would be the proper tool to represent a result type, aka Either Monad. :-) I would be surprised if they make it into 8.6, but 8.7 is not out of the realm of possibility. And I keep pushing for integrating Either into the language via checked lightweight exceptions, but so far no one on Internals is listening to me. :-P

I would probably vote No on a PSR interface at this point. It would be the wrong approach, and the ecosystem is not quite ready yet.

Of course, either way we'd still need buy-in from major players and a working group. That step needs to come *before* detailed design work happens, so the current discussion on the PR is not particularly relevant.

--Larry Garfield
Reply all
Reply to author
Forward
0 new messages