-
Notifications
You must be signed in to change notification settings - Fork 2.8k
New PSR for "Error and Result Handling" interfaces #1344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
adb3be8
82f9440
7b332be
9a019aa
9793d5e
263d367
9a56db9
2ce8ecf
6c15f17
a49cf19
6839a96
06881d7
95da5f8
f5bf499
2e0180d
dd2ec69
2b70545
9965b15
e1c191e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| # Error & Result Handling Meta Document | ||
|
|
||
| ## 1. Summary | ||
|
|
||
| This PSR proposes standard interfaces for representing operation results and errors in a type-safe, composable manner. It defines: | ||
|
|
||
| - A `ResultInterface` representing the outcome of an operation (success or failure and the actual result value) | ||
| - An `ErrorInterface` representing detailed error information | ||
| - Standard patterns for chaining, transforming, and inspecting results | ||
|
|
||
| This actually enables libraries to return predictabe, structured outcomes without exceptions while maintaining interoperability. | ||
|
|
||
| Note: Error messages follow PSR-3 placeholder semantics. This enables deferred formatting, localization, and structured validation output without imposing a formatter or translator. | ||
|
|
||
| Note #2: `ResultFactoryInterface` is OPTIONAL. Libraries and apps MAY use direct construction or static named constructors (like Result::success()), if they control the implementation. The factory interface exists only to enable interoperable creation when implementations are abstracted. | ||
|
|
||
| Note #3: This PSR implements the well-established Either monad (from functional programming), commonly used in Haskell, Scala, Rust, and Kotlin. The interface provides map(fmap), mapError(leftMap), then(flatMap/bind), and fold(either)—all standard Either operations. | ||
|
|
||
| ## 2. Why Bother? | ||
|
|
||
| ### Current Problems | ||
|
|
||
| 1. Libraries use mixed approaches (exceptions, error codes, null returns) | ||
| 2. PHP's type system cant distinguish between valid returns and errors | ||
| 3. Simple `false` or `null` returns don't carry error details | ||
| 4. Each framework implements its own result/error objects | ||
| 5. Not all failures are exceptional; some are expected business cases | ||
|
|
||
| ### Benefits | ||
|
|
||
| - Chain operations without try-catch nesting | ||
| - Preserve error context across bondaries | ||
| - Libraries can share error semantics | ||
| - Avoids exception overhead for expected failures | ||
| - Distinguishes between technical errors and business rule violations | ||
| - _PHPStan/PHPCS/Psalm can verify error handling_ | ||
|
|
||
| ## 3. Scope | ||
|
|
||
| ### 3.1 Goals | ||
|
|
||
| - Define standard interfaces for operation results | ||
| - Provide base implementation for common use cases | ||
| - Enable interoperability between error-aware libraries | ||
| - Support both synchronous and asynchronous patterns | ||
| - Integrate with existing PSRs (PSR-3 logging, PSR-14 events) | ||
| - Be compatible with PHP 7.4+ type systems | ||
|
|
||
| ### 3.2 Non-Goals | ||
|
|
||
| - **Not** replacing exceptions for truly exceptional conditions | ||
| - **Not** prescribing logging or monitoring implementation | ||
| - **Not** definng transport/serialization formats | ||
| - **Not** handling global error/exception handlers | ||
| - **Not** replacing HTTP status codes in PSR-7/15/18 | ||
|
|
||
| ## 4. Approaches | ||
|
|
||
| ### 4.1 Chosen Approach: Tagged Union with Monadic Methods | ||
|
|
||
| **Why this approach:** | ||
|
|
||
| - PHP's type system supports it via `isSuccess()`/`isFailure()` discrimination | ||
| - Provides both imperative `if` and functional `map()` access patterns | ||
| - Familiar to developers from modern languges (Rust, Kotlin, Swift...) | ||
| - Maintains PHP's pragmatic balance between OOP and functional patterns | ||
| - Can be extended for async/await patterns when Fibers mature | ||
|
|
||
| ## 5. Backward Compatibility | ||
|
|
||
| For gradual adoption, libraries MAY: | ||
|
|
||
| 1. Add new methods returning `ResultInterface` alongside old methods | ||
| 2. Provide adapters from exceptions to results | ||
|
|
||
| ## 6. Error Classification | ||
|
|
||
| Implementations MAY extend error types: | ||
|
|
||
| - `ValidationError` (business rule violations) | ||
| - `NotFoundError` (missing resources) | ||
| - `ConflictError` (state conflicts) | ||
| - `AuthenticationError` (auth failures) | ||
| - `AuthorizationError` (permission issues) | ||
| - `InfrastructureError` (system failures) | ||
|
|
||
| ## 7. Typing and Generics | ||
|
|
||
| Since PHP does not currently support generics at the language level: | ||
|
|
||
| - This PSR uses phpdoc-based generics (@template) to express the relationship between success values and error values, following established practice in the PHP ecosystem. | ||
| - Implementations are expected to enforce the invariant that a Result contains either a success value or an error, but never both. | ||
| - Consumers SHOULD rely on `isSuccess()` / `isFailure()` (or equivalent terminal operations such as `fold()`) before accessing the contained value. | ||
| - This PSR does not require nor encourage implementors to create separate result classes for each possible value or error type. | ||
|
|
||
| ## 8. Namespaces | ||
|
|
||
| - `ErrorInterface` is defined in the `Psr\Error` namespace to allow error objects to be reused independently of `ResultInterface`. | ||
| - Errors in this PSR are plain value objects that may be created, transformed, transported, logged, or rendered without necessarily being wrapped in a `Result`. | ||
| - `ResultInterface` composes an error but does not own the error abstraction. Keeping these concerns in separate namespaces avoids coupling error representation to a single control-flow mechanism and preserves flexibility for future use-cases. | ||
|
|
||
| ## 9. People | ||
|
|
||
| - **PHP-FIG team** | ||
| - **Proposer:** Yousha Aleayoub - [blog](https://yousha.blog.ir) | ||
| - **Discussion Group:** https://groups.google.com/g/php-fig/c/OpEuvGERM5A |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,262 @@ | ||||||||||||||||
| # DRAFT: Error and Result Handling | ||||||||||||||||
|
|
||||||||||||||||
| ## 1. Overview | ||||||||||||||||
|
|
||||||||||||||||
| This document describes standard interfaces for representing operation results in PHP applications. | ||||||||||||||||
|
|
||||||||||||||||
| The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119). | ||||||||||||||||
|
|
||||||||||||||||
| ## 2. Definitions | ||||||||||||||||
|
|
||||||||||||||||
| - **Result**: The outcome of an operation that may succeed or fail. | ||||||||||||||||
| - **Success Result**: A result containing a succesful value. | ||||||||||||||||
| - **Failure Result**: A result containing error information. | ||||||||||||||||
| - **Error**: Structured information about why an operation failed. | ||||||||||||||||
|
|
||||||||||||||||
| ## 3. Interfaces | ||||||||||||||||
|
|
||||||||||||||||
| ### 3.1 `ErrorInterface` | ||||||||||||||||
|
|
||||||||||||||||
| ```php | ||||||||||||||||
| <?php | ||||||||||||||||
|
|
||||||||||||||||
| namespace Psr\Error; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Represents structured error information. | ||||||||||||||||
| */ | ||||||||||||||||
| interface ErrorInterface extends \Throwable | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems problematic to me. One of the stated goals of this PSR is to avoid the overhead of Throwable/Exception, so it doesn't make sense for the error to extend Throwable. To make it easier to "throw an error" then I would suggest adding a method throw $error->getException();
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would reject that, because: 1- My goal was "Avoids exception overhead for expected failures", not Throwable. Note that And I think ?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
only if toThrowable() is called, which may not be needed in a lot of cases and there's still the issue that
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad, let's fix it We have 2 ways: 1: interface ErrorInterface extends \Throwable
{
public function getCode(): int | string;
}2: interface ErrorInterface extends \Throwable
{
public function getErrorCode(): string;
}Which one is more acceptable? |
||||||||||||||||
| { | ||||||||||||||||
| /** | ||||||||||||||||
| * Returns a machine-readable error code. | ||||||||||||||||
| * | ||||||||||||||||
| * This code is a stable identifier intended for programmatic use | ||||||||||||||||
| * (like UUIDs, HTTP status codes, enum-like values, localization, PDO/SQLSTATE), and is not related | ||||||||||||||||
| * to Throwable::getCode(). | ||||||||||||||||
| */ | ||||||||||||||||
| public function getCode(): string; | ||||||||||||||||
Yousha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Returns a human-readable error message. | ||||||||||||||||
| * | ||||||||||||||||
| * The message MAY contain placeholders in the form `{placeholder}`. | ||||||||||||||||
| * Context values MAY be provided to replace placeholders for display, | ||||||||||||||||
| * logging, or translation purposes. | ||||||||||||||||
| * | ||||||||||||||||
| * Message formatting and translation are the responsibility of the consumer. | ||||||||||||||||
| */ | ||||||||||||||||
| public function getMessage(): string; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Returns structured context data for the error. | ||||||||||||||||
| * | ||||||||||||||||
| * @return array<string, mixed> | ||||||||||||||||
| */ | ||||||||||||||||
| public function getContext(): array; | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than an array, which has poor type validation, maybe it would make more sense to have this be a value getter? As in: public function getContext(string $key): mixed;This would imply another modification: public function withContext(string $key, mixed $value): self;While it may be slightly less convenient, this signature avoids array "blobs" that are hard to validate and provides a more structured way to access context. For instance, an error could define context keys as constants: $minLength = $error->getContext($error::MIN_LENGTH);
$maxLength = $error->getContext($error::MAX_LENGTH);
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tbh the validation here is not really better, static analysis tools would already warn if you didn't use string for the keys. not everyone use them but 🤷 and having the context was meant to easily be able to log the error with a PSR LoggerInterface afaict |
||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Returns the underlying/previous error. | ||||||||||||||||
| */ | ||||||||||||||||
| public function getPrevious(): ?ErrorInterface; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Creates a new instance with additional context. | ||||||||||||||||
| * | ||||||||||||||||
| * @param array<string, mixed> $context | ||||||||||||||||
| */ | ||||||||||||||||
| public function withContext(array $context): self; | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure that should be part of the interface
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, should be: Minimal example: $result = myOperation()
->mapError(fn($error) => $error->withContext(['step' => 'validation']))
->mapError(fn($error) => $error->withContext(['service' => 'user-api']));
// So final error contains both 'step' and 'service' in context.
if ($result->isFailure()) {
print_r($result->getError()->getContext());
// ['step' => 'validation', 'service' => 'user-api']
}Without it, error context is frozen at creation => breaking composability ? |
||||||||||||||||
| } | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ### 3.2 `ResultInterface` | ||||||||||||||||
|
|
||||||||||||||||
| ```php | ||||||||||||||||
| <?php | ||||||||||||||||
|
|
||||||||||||||||
| namespace Psr\Result; | ||||||||||||||||
|
|
||||||||||||||||
| use Psr\Error\ErrorInterface; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Represents the result of an operation. | ||||||||||||||||
| * | ||||||||||||||||
| * Execution rules: | ||||||||||||||||
| * - If the result is a failure, `map()` and `then()` MUST NOT call their callbacks. | ||||||||||||||||
| * - If the result is a success, `mapError()` MUST NOT call its callback. | ||||||||||||||||
| * - `then()` MUST NOT wrap results; it must flatten them. | ||||||||||||||||
| * | ||||||||||||||||
| * @template TValue | ||||||||||||||||
| * @template TError of ErrorInterface | ||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest allowing to use anything as error types, forcing to have a Throwable is not that great imo (and a bit costly because creating an exception is not as cheap as creating simpler objects)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, allowing “anything” as error types is not generic, it is untyped... a PSR(mine) that allows mixed errors is useless for composition. That return type allows to use codes, messages, context and avoids mixed & arrays-of-arrays hell.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
and enforcing also, not enforcing anything is not completely untyped, you can still have the template type and benefit from cases where the result do implement
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, other PSRs: From my meta document: Integrate with existing PSRs (PSR-3 logging, PSR-14 events)
It is, because no stack trace unless you call class ValidationError implements \Throwable
{
public function getTrace(): array { return []; } // <--- zero-cost like oxygen :DNo stack trace is ever generated, no internal PHP exception machinery is triggered, so object is a true plain value object
And not enforcing devs to use But I'm still okay with you to remove that...
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
well, for PSR-3, it says:
so, if the error type is not an exception you can log it in another field, or even in the exception field if you're feeling adventurous as implementation of logger must check that it's indeed an exception before using it as such for psr-14, it mentions throwing exceptions, so yeah if you wrap it in a result with try() the error type will be an exception. doesn't need to be forced to be an exception in Result for that to work and same for psr-18, it's emitting exceptions, not taking results like things as input either
you can't do that it's not valid PHP code : https://3v4l.org/ekl3s#vnull
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes I know, I just wanted to use pseudocode to show what I mean So remove |
||||||||||||||||
| */ | ||||||||||||||||
| interface ResultInterface | ||||||||||||||||
| { | ||||||||||||||||
| /** | ||||||||||||||||
| * Returns true if the operation was successful. | ||||||||||||||||
| */ | ||||||||||||||||
| public function isSuccess(): bool; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Returns true if the operation failed. | ||||||||||||||||
| */ | ||||||||||||||||
| public function isFailure(): bool; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Returns the success value | ||||||||||||||||
| * | ||||||||||||||||
| * @return TValue | ||||||||||||||||
| * @throws \BadMethodCallException If result is a failure. | ||||||||||||||||
| */ | ||||||||||||||||
| public function getValue(): mixed; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Returns the error if operation failed. | ||||||||||||||||
| * | ||||||||||||||||
| * @return TError|null | ||||||||||||||||
| */ | ||||||||||||||||
| public function getError(): ?ErrorInterface; | ||||||||||||||||
Yousha marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+114
to
+116
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the goal is to avoid
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TBH, I took this design from Rust So my 1- It is only valid to call when Note that this PSR avoids Kotlin docs:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it stays
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
there's also the option to have I took advantage of that in my Result implementation attempt : https://github.com/texthtml/maybe/tree/main/src/Result and the result is quite nice, Psalm, PHPStorm and mago can give good insight. for exemple, combined with those annotations: |
||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Applies a transformation to the success value. | ||||||||||||||||
| * | ||||||||||||||||
| * Called only if the result is successful. | ||||||||||||||||
| * Failures are propagated unchanged. | ||||||||||||||||
| * | ||||||||||||||||
| * @template TNew | ||||||||||||||||
| * @param callable(TValue): TNew $transform | ||||||||||||||||
| * A callable that receives the success value and returns a new value. | ||||||||||||||||
| * @return ResultInterface<TNew, TError> | ||||||||||||||||
| */ | ||||||||||||||||
| public function map(callable $transform): ResultInterface; | ||||||||||||||||
Yousha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Applies a transformation to the error. | ||||||||||||||||
| * | ||||||||||||||||
| * Called only if the result is a failure. | ||||||||||||||||
| * Success values are propagated unchanged. | ||||||||||||||||
| * | ||||||||||||||||
| * @template TNewError of ErrorInterface | ||||||||||||||||
| * @param callable(TError): TNewError $transform | ||||||||||||||||
| * A callable that receives the error and returns a new error. | ||||||||||||||||
| * @return ResultInterface<TValue, TNewError> | ||||||||||||||||
| */ | ||||||||||||||||
| public function mapError(callable $transform): ResultInterface; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Chains another operation that returns a Result. | ||||||||||||||||
| * | ||||||||||||||||
| * Called only if the result is successful. | ||||||||||||||||
| * The returned Result is flattened (no nesting). | ||||||||||||||||
| * | ||||||||||||||||
| * @template TNew | ||||||||||||||||
| * @param callable(TValue): ResultInterface<TNew, TError> $operation | ||||||||||||||||
| * A callable that receives the success value and returns a Result. | ||||||||||||||||
| * @return ResultInterface<TNew, TError> | ||||||||||||||||
| */ | ||||||||||||||||
| public function then(callable $operation): ResultInterface; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Resolves the result into a single value. | ||||||||||||||||
| * | ||||||||||||||||
| * Exactly one callback is called. | ||||||||||||||||
| * This terminates the Result pipeline. | ||||||||||||||||
| * | ||||||||||||||||
| * @template TReturn | ||||||||||||||||
| * @param callable(TValue): TReturn $onSuccess | ||||||||||||||||
| * @param callable(TError): TReturn $onFailure | ||||||||||||||||
| * @return TReturn | ||||||||||||||||
| */ | ||||||||||||||||
| public function fold(callable $onSuccess, callable $onFailure): mixed; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Returns the success value or a default if failed. | ||||||||||||||||
| * | ||||||||||||||||
| * Does not expose the error. | ||||||||||||||||
| * | ||||||||||||||||
| * @param TValue $default | ||||||||||||||||
| * The value to return if the result is a failure. | ||||||||||||||||
| * @return TValue | ||||||||||||||||
| */ | ||||||||||||||||
| public function getValueOr(mixed $default): mixed; | ||||||||||||||||
Yousha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| } | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ### 3.3 `ResultFactoryInterface` | ||||||||||||||||
|
|
||||||||||||||||
| ```php | ||||||||||||||||
| <?php | ||||||||||||||||
|
|
||||||||||||||||
| namespace Psr\Result; | ||||||||||||||||
|
|
||||||||||||||||
| use Psr\Error\ErrorInterface; | ||||||||||||||||
|
|
||||||||||||||||
| interface ResultFactoryInterface | ||||||||||||||||
Yousha marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
| { | ||||||||||||||||
| /** | ||||||||||||||||
| * Create a successful result. | ||||||||||||||||
| * | ||||||||||||||||
| * @template T | ||||||||||||||||
| * @param T $value | ||||||||||||||||
| * @return ResultInterface<T, ErrorInterface> | ||||||||||||||||
| */ | ||||||||||||||||
| public function success(mixed $value): ResultInterface; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Create a failed result. | ||||||||||||||||
| * | ||||||||||||||||
| * @template E of ErrorInterface | ||||||||||||||||
| * @param E $error | ||||||||||||||||
| * @return ResultInterface<mixed, E> | ||||||||||||||||
| */ | ||||||||||||||||
| public function failure(ErrorInterface $error): ResultInterface; | ||||||||||||||||
|
|
||||||||||||||||
| /** | ||||||||||||||||
| * Create result from a callable that may throw. | ||||||||||||||||
| * | ||||||||||||||||
| * @template T | ||||||||||||||||
| * @param callable(): T $operation | ||||||||||||||||
| * @param callable(\Throwable): ErrorInterface $errorMapper | ||||||||||||||||
| * @return ResultInterface<T, ErrorInterface> | ||||||||||||||||
| */ | ||||||||||||||||
| public function try(callable $operation, callable $errorMapper): ResultInterface; | ||||||||||||||||
| } | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ## 4. Usage Examples | ||||||||||||||||
|
|
||||||||||||||||
| ### 4.1 Basic Usage | ||||||||||||||||
|
|
||||||||||||||||
| ```php | ||||||||||||||||
| $result = $userRepository->findById($id); | ||||||||||||||||
|
|
||||||||||||||||
| if ($result->isSuccess()) { | ||||||||||||||||
| $user = $result->getValue(); | ||||||||||||||||
| echo "Found: " . $user->getName(); | ||||||||||||||||
| } else { | ||||||||||||||||
| $error = $result->getError(); | ||||||||||||||||
| logError($error->getCode(), $error->getContext()); | ||||||||||||||||
| } | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ### 4.2 Functional paradigm | ||||||||||||||||
|
|
||||||||||||||||
| ```php | ||||||||||||||||
| $email = $userRepository->findById($id) | ||||||||||||||||
| ->map(fn($user) => $user->getEmail()) | ||||||||||||||||
| ->getValueOr('default@example.com'); | ||||||||||||||||
| ``` | ||||||||||||||||
|
|
||||||||||||||||
| ### 4.3 Chaining Operations | ||||||||||||||||
|
|
||||||||||||||||
| ```php | ||||||||||||||||
| $result = $validator->validate($input) | ||||||||||||||||
| ->then(fn($v) => $repository->save($v)) | ||||||||||||||||
| ->then(fn($e) => $notifier->notifyCreated($e)) | ||||||||||||||||
| ->mapError(fn($err) => new PublicError($err->getMessage())); | ||||||||||||||||
|
|
||||||||||||||||
| // At the end... | ||||||||||||||||
| if ($result->isSuccess()) { | ||||||||||||||||
| $finalEntity = $result->getValue(); // From notifier. | ||||||||||||||||
| } else { | ||||||||||||||||
| $publicError = $result->getError(); // Already transformed to PublicError. | ||||||||||||||||
| } | ||||||||||||||||
| ``` | ||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The namespace for ErrorInterface and ResultInterface should be the same, in my opinion:
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be and I'm okay by your change, but here is my reason:
For clarity and separation of concerns, to load only Error but no Result, & following PSR conventions...
So future PSRs can depend on Error only, without inheriting Result semantics.
I have added a Namespaces section in meta document: https://github.com/php-fig/fig-standards/pull/1344/files#diff-99b9802e69456918264b6be5f78aa91cffedf996d1fe910b0e817c4d30d9be72R92
So
ErrorInterfacedefined inPsr\Errorto allow its reuse independently ofResultInterface... including validation, err transport, and normalization use cases.And
ResultInterfacecomposes an error but does NOT own the error abstraction.?