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

1.0.0 2025-09-29 18:23 UTC

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 Err
  • expect(string $message): T - like unwrap(), but with your message
  • error(): ?Throwable - returns the error on Err, null on Ok
  • valueOr(T $default): T - returns the value or a default on error
  • map(callable(T): S): ResultInterface<S> — transform an Ok value; no-op on Err
  • mapErr(callable(Throwable): Throwable): ResultInterface<T> - transform the error; no-op on Ok
  • andThen(callable(T): ResultInterface<S>): ResultInterface<S> - flat-map for chaining operations that return Result
  • orElse(callable(Throwable): ResultInterface<T>): ResultInterface<T> - recover from Err by producing a new Result
  • tap(callable(T): void): $this - side-effect on Ok; no-op on Err
  • tapErr(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.