A collection of useful monads for PHP 8.3+. Inspired by Rust's powerful type system and functional programming patterns.
- 🦀 Rust-inspired API - Familiar methods for those coming from Rust
- đź”’ Type-safe - Full PHPStan level 9 support with generics
- đź§Ş Well-tested - Comprehensive test suite
- 📦 Zero dependencies - Lightweight and focused
- 🎯 Three core monads:
Option<T>- Represent optional values without nullResult<T, E>- Handle errors without exceptionsLazy<T>- Defer computation until needed
You can install the package via Composer:
composer require gosuperscript/monads- PHP 8.3 or higher
The Option type represents an optional value: every Option is either Some and contains a value, or None, and does not. This is a safer alternative to using null.
use function Superscript\Monads\Option\{Some, None};
// Create an Option
$some = Some(42);
$none = None();
// Check if value exists
$some->isSome(); // true
$none->isNone(); // true
// Transform the value
$doubled = Some(21)->map(fn($x) => $x * 2); // Some(42)
$empty = None()->map(fn($x) => $x * 2); // None
// Provide default values
Some(42)->unwrapOr(0); // 42
None()->unwrapOr(0); // 0
// Chain operations
Some(10)
->filter(fn($x) => $x > 5)
->map(fn($x) => $x * 2)
->unwrapOr(0); // 20
// Convert to Result
Some(42)->okOr("error"); // Ok(42)
None()->okOr("error"); // Err("error")isSome()/isNone()- Check if the option contains a valueisSomeAnd(callable $predicate)- Check if Some and matches predicatemap(callable $f)- Transform the contained valuefilter(callable $f)- Filter based on a predicateand(Option $other)/or(Option $other)- Combine optionsandThen(callable $f)- Chain operations (flatMap)unwrap()- Get the value (throws if None)unwrapOr($default)- Get the value or a defaultunwrapOrElse(callable $f)- Get the value or compute a defaultexpect(string|Throwable $message)- Unwrap with custom error message
Result<T, E> is the type used for returning and propagating errors. It is either Ok(T), representing success and containing a value, or Err(E), representing error and containing an error value.
use function Superscript\Monads\Result\{Ok, Err, attempt};
// Create Results
$ok = Ok(42);
$err = Err("something went wrong");
// Check the result
$ok->isOk(); // true
$err->isErr(); // true
// Transform success values
$doubled = Ok(21)->map(fn($x) => $x * 2); // Ok(42)
$stillErr = Err("error")->map(fn($x) => $x * 2); // Err("error")
// Transform error values
$recovered = Err("error")->mapErr(fn($e) => "recovered"); // Err("recovered")
// Handle both cases
$result = Ok(10)->match(
err: fn($e) => "Error: $e",
ok: fn($x) => "Success: $x"
); // "Success: 10"
// Chain operations
Ok(10)
->map(fn($x) => $x * 2)
->andThen(fn($x) => $x > 15 ? Ok($x) : Err("too small"))
->unwrapOr(0); // 20
// Convert to Option
Ok(42)->ok(); // Some(42)
Err("e")->ok(); // None()
// Safely execute code that might throw
$result = attempt(fn() => json_decode($json, flags: JSON_THROW_ON_ERROR));
// Returns: Result<mixed, Throwable>isOk()/isErr()- Check if the result is success or errormap(callable $f)- Transform the success valuemapErr(callable $f)- Transform the error valuemapOr($default, callable $f)- Transform or provide defaultmapOrElse(callable $default, callable $f)- Transform or compute defaultmatch(callable $err, callable $ok)- Handle both casesand(Result $other)/or(Result $other)- Combine resultsandThen(callable $f)- Chain operations (flatMap)unwrap()- Get the success value (throws if Err)unwrapErr()- Get the error value (throws if Ok)unwrapOr($default)- Get the value or a defaultunwrapOrElse(callable $f)- Get the value or compute a defaultexpect(string|Throwable $message)- Unwrap with custom error message
The Lazy type allows you to defer the execution of a computation until its result is actually needed.
use Superscript\Monads\Lazy\Lazy;
// Create a lazy computation
$lazy = Lazy::of(fn() => expensiveComputation());
// The computation hasn't run yet...
// Evaluate when needed (memoized)
$result = $lazy->evaluate(); // Runs the computation
$cached = $lazy->evaluate(); // Returns cached result
// Practical example: lazy database query
$users = Lazy::of(fn() => DB::query("SELECT * FROM users"));
if ($needUsers) {
$data = $users->evaluate(); // Query runs only if needed
}Both Option and Result support collecting arrays of values:
use function Superscript\Monads\Option\{Some, None};
use function Superscript\Monads\Result\{Ok, Err};
// Collect Options - returns first None or Some(array)
Option::collect([Some(1), Some(2), Some(3)]); // Some([1, 2, 3])
Option::collect([Some(1), None(), Some(3)]); // None()
// Collect Results - returns first Err or Ok(array)
Result::collect([Ok(1), Ok(2), Ok(3)]); // Ok([1, 2, 3])
Result::collect([Ok(1), Err("e"), Ok(3)]); // Err("e")use Superscript\Monads\Option\Option;
function getUser(int $id): Option {
$user = DB::find('users', $id);
return Option::from($user); // Returns None if null
}
$username = getUser(123)
->map(fn($user) => $user->name)
->unwrapOr('Guest');use function Superscript\Monads\Result\{Ok, Err, attempt};
function divide(int $a, int $b): Result {
return $b === 0
? Err("Division by zero")
: Ok($a / $b);
}
$result = divide(10, 2)
->map(fn($x) => $x * 2)
->unwrapOr(0); // 10
$error = divide(10, 0)
->map(fn($x) => $x * 2)
->unwrapOr(0); // 0use function Superscript\Monads\Result\{Ok, Err};
function processData(array $data): Result {
return Ok($data)
->andThen(fn($d) => validateData($d))
->andThen(fn($d) => transformData($d))
->andThen(fn($d) => saveData($d));
}
$result = processData($input)->match(
err: fn($e) => response()->json(['error' => $e], 400),
ok: fn($d) => response()->json(['data' => $d], 200)
);The package uses Pest for testing:
# Run tests
vendor/bin/pest
# Run type checking
vendor/bin/phpstan
# Run code style fixer
vendor/bin/pintThis library provides full PHPStan support with generic types. The testing utilities include:
use Superscript\Monads\Result\Testing\ComparesResults;
use Superscript\Monads\Option\Testing\ComparesOptions;
class MyTest extends TestCase {
use ComparesResults;
use ComparesOptions;
public function test_example() {
// Custom assertions
$this->assertOk(Ok(42));
$this->assertErr(Err("error"));
$this->assertSome(Some(42));
$this->assertNone(None());
// PHPUnit constraints
$this->assertThat(Ok(42), $this->isOk());
$this->assertThat(Err("e"), $this->isErr());
}
}Monads help you write more predictable and maintainable code by:
- Making errors explicit - No hidden nulls or uncaught exceptions
- Enabling composition - Chain operations cleanly with
mapandandThen - Improving type safety - Let PHPStan catch errors at analysis time
- Reducing boilerplate - Less null checking and try-catch blocks
This library is heavily inspired by Rust's Option and Result types, bringing similar patterns to PHP.
Please see CHANGELOG for more information on what has changed recently.
Contributions are welcome! Please feel free to submit a Pull Request.
The MIT License (MIT). Please see License File for more information.