Result Type
#Result Type
Result Type provides small, typed objects for representing operations that may succeed, fail, or return no value.
Instead of throwing exceptions for expected outcomes, you may return a Result or Option and handle each case where it occurs.
use NunoMaduro\ResultType\ResultType; $result = ResultType::try(fn () => json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR)); $name = $result->match( ok: fn (array $payload): string => $payload['name'], err: fn (): string => 'Guest', );
Result Type requires PHP 8.5+.
#Installation
You may install the package via Composer:
composer require nunomaduro/result-type
#Introduction
Result Type offers two main types:
| Type | States | Use it when |
|---|---|---|
Result |
Ok or Err |
An operation may succeed with a value or fail with an error. |
Option |
Some or None |
A value may or may not exist, and absence is not an error. |
Result and Option values are immutable. Methods such as map, mapErr, andThen, orElse, and filter return a new value instead of changing the original one.
#Creating Results
You may create a successful result using the ResultType::ok method, or a failed result using the ResultType::err method:
use NunoMaduro\ResultType\ResultType; $result = ResultType::ok(42); $error = ResultType::err('The user could not be created.');
The ResultType::try method may be used to convert exception-throwing code into a Result. If the callback completes successfully, an Ok result will be returned. If the callback throws, an Err result containing the thrown exception will be returned:
use NunoMaduro\ResultType\ResultType; $result = ResultType::try(fn () => json_decode($json, associative: true, flags: JSON_THROW_ON_ERROR)); if ($result->isErr()) { report($result->unwrapErr()); }
#Creating Options
You may create an option containing a value using the OptionType::some method. To represent an absent value, use the OptionType::none method:
use NunoMaduro\ResultType\OptionType; $name = OptionType::some('Taylor'); $missing = OptionType::none();
The OptionType::fromNullable method is useful when working with values that may be null:
$name = OptionType::fromNullable($request->input('name'));
#Matching Values
The most common way to consume a Result or Option is with the match method. The method receives one callback for each possible state and returns the value from the matching callback:
$message = ResultType::try(fn (): string => 'Profile updated.')->match( ok: fn (string $message): string => $message, err: fn (\Throwable $throwable): string => $throwable->getMessage(), );
Options may be matched in the same way:
$name = OptionType::fromNullable($user->name)->match( some: fn (string $name): string => $name, none: fn (): string => 'Anonymous', );
When you only need to branch by predicates, PHP's native match expression also works well with isOk, isErr, isSome, isNone, and the predicate helpers:
$label = match (true) { $result->isOkAnd(fn (array $user): bool => $user['role'] === 'admin') => 'Administrator', $result->isOk() => 'User', $result->isErr() => 'Guest', };
For most value extraction, prefer the package's match method since it unwraps the correct branch for you and keeps both states visible at the call site.
#Unwrapping Values
You may use unwrap to retrieve the value inside an Ok or Some instance:
$value = ResultType::ok(42)->unwrap(); $name = OptionType::some('Taylor')->unwrap();
If unwrap is called on an Err or None, an exception will be thrown. If that is not the behavior you want, use unwrapOr to provide a default value:
$value = ResultType::err('Invalid amount.')->unwrapOr(0); $name = OptionType::none()->unwrapOr('Anonymous');
You may also lazily compute the fallback value using unwrapOrElse:
$message = ResultType::err('Invalid amount.')->unwrapOrElse( fn (string $error): string => "Please try again: {$error}", ); $name = OptionType::none()->unwrapOrElse(fn (): string => config('app.name'));
The expect and expectErr methods behave like unwrap and unwrapErr, but allow you to provide the exception message:
$user = $result->expect('The user should have been loaded.'); $error = $result->expectErr('The operation was expected to fail.');
#Transforming Results
The map method transforms the value inside an Ok result. If the result is an Err, the callback will not be executed:
$result = ResultType::ok(21) ->map(fn (int $value): int => $value * 2); $result->unwrap(); // 42
The mapErr method transforms the error inside an Err result. If the result is Ok, the callback will not be executed:
$result = ResultType::err('not_found') ->mapErr(fn (string $error): string => strtoupper($error)); $result->unwrapErr(); // NOT_FOUND
The mapOr and mapOrElse methods are convenient when you want to transform an Ok value into a plain value while providing a fallback for Err:
$score = ResultType::ok(21)->mapOr(0, fn (int $value): int => $value * 2); $message = ResultType::err('missing')->mapOrElse( default: fn (string $error): string => "Could not load: {$error}", callback: fn (string $value): string => $value, );
#Chaining Results
The andThen method may be used to chain operations that each return a Result. If any step returns an Err, the remaining steps will not be executed:
use NunoMaduro\ResultType\Contracts\Result; use NunoMaduro\ResultType\ResultType; function findUser(int $id): Result { // ... } function sendReceipt(User $user): Result { // ... } $result = ResultType::ok($userId) ->andThen(fn (int $id): Result => findUser($id)) ->andThen(fn (User $user): Result => sendReceipt($user));
The orElse method allows you to recover from an Err by returning another Result:
$result = ResultType::err('primary_failed') ->orElse(fn (string $error): Result => ResultType::ok('Recovered value'));
You may also combine results directly using and and or:
ResultType::ok('first')->and(ResultType::ok('second'))->unwrap(); // second ResultType::err('failed')->or(ResultType::ok('fallback'))->unwrap(); // fallback
#Transforming Options
The map method transforms the value inside a Some option. If the option is None, the callback will not be executed:
$option = OptionType::some('taylor') ->map(fn (string $name): string => ucfirst($name)); $option->unwrap(); // Taylor
The filter method keeps the Some value only when the callback returns true:
$option = OptionType::some(42) ->filter(fn (int $value): bool => $value > 10);
The andThen method may be used to chain operations that each return an Option:
$timezone = OptionType::fromNullable($user->profile) ->andThen(fn (Profile $profile) => OptionType::fromNullable($profile->timezone));
The orElse method allows you to provide another option when the current option is None:
$name = OptionType::fromNullable($user->name) ->orElse(fn () => OptionType::some('Anonymous'));
#Converting Between Result and Option
Results may be converted to options using the ok and err methods:
$value = ResultType::ok(42)->ok(); // Some(42) $error = ResultType::ok(42)->err(); // None $value = ResultType::err('oops')->ok(); // None $error = ResultType::err('oops')->err(); // Some('oops')
Options may be converted to results using okOr and okOrElse:
$result = OptionType::fromNullable($user) ->okOr('User not found.'); $result = OptionType::fromNullable($user) ->okOrElse(fn (): string => "User [{$id}] not found.");
#Inspecting Values
The inspect and inspectErr methods are useful for logging or debugging without changing the result:
$result = ResultType::try(fn () => $client->send($request)) ->inspect(function (Response $response): void { logger()->info('Request sent.', [ 'status' => $response->status(), ]); }) ->inspectErr(fn (\Throwable $throwable): void => report($throwable));
Options also provide inspect for running a side effect when the option is Some:
$option = OptionType::fromNullable($user->email) ->inspect(fn (string $email): void => logger()->info("Email found: {$email}"));
#Nested Values
The flatten method removes one level of nesting from nested results or options:
$result = ResultType::ok(ResultType::ok(42))->flatten(); $option = OptionType::some(OptionType::some('Taylor'))->flatten();
The transpose method converts between nested Result and Option shapes:
$option = ResultType::ok(OptionType::some(42))->transpose(); $result = OptionType::some(ResultType::ok(42))->transpose();
#Iterating Values
The iter method returns an iterable containing the successful or present value. Empty states return an empty iterable:
foreach (ResultType::ok(42)->iter() as $value) { // 42 } foreach (OptionType::none()->iter() as $value) { // This block will not run. }
#Converting to Arrays
You may convert results and options to arrays using the toArray method:
ResultType::ok(42)->toArray(); // ['ok' => true, 'value' => 42] ResultType::err('oops')->toArray(); // ['ok' => false, 'error' => 'oops'] OptionType::some(42)->toArray(); // ['some' => true, 'value' => 42] OptionType::none()->toArray(); // ['some' => false]
#Available Methods
Results provide the following methods: isOk, isErr, isOkAnd, isErrAnd, unwrap, unwrapErr, unwrapOr, unwrapOrElse, expect, expectErr, map, mapErr, mapOr, mapOrElse, and, or, andThen, orElse, match, inspect, inspectErr, ok, err, flatten, transpose, iter, and toArray.
Options provide the following methods: isSome, isNone, isSomeAnd, unwrap, unwrapOr, unwrapOrElse, expect, map, mapOr, mapOrElse, okOr, okOrElse, and, or, andThen, orElse, filter, match, inspect, flatten, transpose, iter, and toArray.
#Development
To keep the codebase formatted, run Pint:
composer lint
To run Rector:
composer refactor
To run static analysis:
composer test:types
To run the unit tests:
composer test:unit
To run the entire test suite:
composer test
Result Type was created by Nuno Maduro and is open-sourced software licensed under the MIT license.