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-10-30 09:05:00 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(): boolisErr(): boolunwrap(): 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.