juststeveking / result
A tiny, framework-agnostic Result type for PHP 8.4 that makes error handling explicit, composable, and testable.
Fund package maintenance!
juststeveking
Installs: 2
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/juststeveking/result
Requires
- php: ^8.4
Requires (Dev)
- laravel/pint: ^1.25.1
- phpstan/phpstan: ^2.1.29
- phpstan/phpstan-strict-rules: ^2.0.7
- phpunit/phpunit: ^12.3.15
- roave/security-advisories: dev-latest
This package is auto-updated.
Last update: 2025-09-30 08:52:56 UTC
README
A tiny, framework-agnostic Result type for PHP that makes error handling explicit, composable, and testable.
- Express success as Ok(value) and failure as Err(Throwable)
- Eliminate nullable return values and ambiguous sentinel values
- Chain operations with map/andThen and recover with mapErr/orElse
- Ergonomic helpers and a small ComparableResult utility
Requirements
- PHP 8.4+
Installation
composer require juststeveking/result
Quick start
<?php use JustSteveKing\Result\Result; use function JustSteveKing\Result\{ok, err, result_match}; // Via the static factory $r1 = Result::ok('hello'); $r2 = Result::err(new RuntimeException('nope')); // Or via helper functions (autoloaded) $r3 = ok(21)->map(fn (int $n) => $n * 2); // Ok(42) // Branching with pattern matching helper $out = result_match( $r3, fn (int $n) => "answer: $n", fn (Throwable $e) => 'error: ' . $e->getMessage(), ); // "answer: 42"
The Result interface
All results implement JustSteveKing\Result\Contracts\ResultInterface<T>
where T
is the success value type (documented via PHPDoc for static analysis).
Key operations:
isOk(): bool
isErr(): bool
unwrap(): T
- returns the value, or throws UnwrapException on Errexpect(string $message): T
- likeunwrap()
, but with your messageerror(): ?Throwable
- returns the error on Err, null on OkvalueOr(T $default): T
- returns the value or a default on errormap(callable(T): S): ResultInterface<S>
— transform an Ok value; no-op on ErrmapErr(callable(Throwable): Throwable): ResultInterface<T>
- transform the error; no-op on OkandThen(callable(T): ResultInterface<S>): ResultInterface<S>
- flat-map for chaining operations that return ResultorElse(callable(Throwable): ResultInterface<T>): ResultInterface<T>
- recover from Err by producing a new Resulttap(callable(T): void): $this
- side-effect on Ok; no-op on ErrtapErr(callable(Throwable): void): $this
- side-effect on Err; no-op on Ok
Examples
Transforming and chaining:
use JustSteveKing\Result\Result; use JustSteveKing\Result\Contracts\ResultInterface; function parseInt(string $raw): ResultInterface { return is_numeric($raw) ? Result::ok((int) $raw) : Result::err(new InvalidArgumentException('not a number')); } $res = parseInt('10') ->map(fn (int $n) => $n * 2) // Ok(20) ->andThen(fn (int $n) => Result::ok($n+1)) // Ok(21) ; $value = $res->valueOr(0); // 21
Recovering from errors:
$res = Result::err(new RuntimeException('boom')) ->mapErr(fn (Throwable $e) => new LogicException('mapped', previous: $e)) ->orElse(fn (Throwable $e) => Result::ok('default')); // $res is Ok('default')
Avoiding exceptions at call sites:
try { $value = mightThrow(); $result = Result::ok($value); } catch (Throwable $e) { $result = Result::err($e); } $safe = $result->valueOr('fallback');
Tapping for side-effects:
ok(['id' => 1]) ->tap(fn (array $data) => error_log('created: ' . $data['id'])) ->tapErr(fn (Throwable $e) => error_log('failed: ' . $e->getMessage()));
Unwrapping (will throw on Err):
$value = Result::ok('x')->unwrap(); // 'x' Result::err(new RuntimeException('no')) ->expect('Failed to compute'); // throws UnwrapException
Helper functions
This package autoloads a few global helpers in the JustSteveKing\Result
namespace:
- ok(mixed $value): ResultInterface
- err(Throwable $error): ResultInterface
- result_match(ResultInterface $result, callable $onOk, callable $onErr): mixed
Example:
use function JustSteveKing\Result\{ok, result_match}; $output = result_match( ok('a'), fn (string $v) => $v . 'b', fn (Throwable $e) => 'error', );
ComparableResult
ComparableResult
is a small utility for success values that are comparable as array-keys (int|string
). It's handy when you need a simple value equality check without unwrapping:
use JustSteveKing\Result\ComparableResult; $cmp = ComparableResult::ok('foo'); $cmp->equals('foo'); // true $cmp->equals('bar'); // false $err = ComparableResult::err(new RuntimeException('nope')); $err->equals('anything'); // false // Access the wrapped Result if you need it $inner = $cmp->inner(); // ResultInterface<int|string>
Error behavior
Calling unwrap()
or expect()
on an Err
throws JustSteveKing\Result\Exceptions\UnwrapException
with your message (for expect
) and the original Throwable as previous
.
Static analysis and generics
The library uses PHPDoc templates (e.g. @template T
) to communicate types to tools like PHPStan/Psalm. You'll get strong typing for ResultInterface<T>
in editors and CI when using these tools.
Tooling
Run the test suite:
composer test
Static analysis and formatting:
composer stan composer pint
Contributing
Bug reports and PRs are welcome. See CONTRIBUTING.md
for guidelines.
License
MIT. See LICENSE
.