Option
composer require php-standard-library/option
The Option component represents a value that may or may not be present. It replaces nullable types (?T) with explicit Some/None semantics, making the absence of a value intentional and impossible to ignore.
Instead of returning null and hoping callers remember to check, you return an Option that forces the caller to handle both cases.
Design
Option<T> is a single readonly class with two states:
Option::some($value)-- A value is presentOption::none()-- No value is present
Convenience functions some(), none(), and from_nullable() are available in the Psl\Option namespace.
Usage
Creating Options
use Psl\Option;
$present = Option\some(42);
$absent = Option\none();
// Bridge from nullable values
$option = Option\from_nullable('hello');
// null becomes None, anything else becomes Some
$fromNull = Option\from_nullable(null);
// $fromNull is None
Checking and Unwrapping
use Psl\Option;
$opt = Option\some(42);
$opt->isSome(); // true
$opt->isNone(); // false
$opt->isSomeAnd(fn(int $v) => $v > 10); // true
$opt->contains(42); // true
// Unwrap the value
$opt->unwrap(); // 42 (throws NoneException if None)
$opt->unwrapOr(0); // 42 (returns default if None)
$opt->unwrapOrElse(fn() => 0); // 42 (lazy default if None)
Transforming Values
use Psl\Option;
$opt = Option\some(5);
// map: transform the inner value
$opt->map(fn(int $v) => $v * 2); // Some(10)
Option\none()->map(fn(int $v) => $v * 2); // None
// andThen: chain operations that return Options (flatMap)
Option\some('hello')
->andThen(fn(string $v) => Option\from_nullable($v !== '' ? $v : null))
->andThen(fn(string $v) => Option\some(strlen($v)));
// Some(5) if non-empty string, None otherwise
// mapOr / mapOrElse: transform with a default
Option\none()->mapOr(fn(int $v) => $v * 2, 0); // Some(0)
Pattern Matching with proceed()
Handle both cases and return a unified result:
use Psl\Option;
$username = 'Alice';
$greeting = Option\from_nullable($username)->proceed(
fn(string $name) => "Welcome back, {$name}!",
fn() => 'Welcome, guest!',
);
Side Effects with apply()
Run a closure on the value without changing the Option:
use Psl\Option;
$result = Option\some('admin')
->apply(fn(string $role) => print "Found role: {$role}\n")
->map(fn(string $role) => strtoupper($role));
Filtering
use Psl\Option;
Option\some(42)->filter(fn(int $v) => $v > 10); // Some(42)
Option\some(42)->filter(fn(int $v) => $v > 100); // None
Combining Options
use Psl\Option;
$a = Option\some(1);
$b = Option\some(2);
$none = Option\none();
$a->and($b); // Some(2) -- returns second if both Some
$none->or($b); // Some(2) -- returns first Some found
$none->orElse(fn() => Option\some(99)); // Some(99)
// Zip: combine into a tuple
$a->zip($b); // Some([1, 2])
$a->zip($none); // None
// ZipWith: combine with a function
$a->zipWith($b, fn(int $x, int $y) => $x + $y); // Some(3)
// Unzip: split a tuple Option
Option\some([1, 2])->unzip(); // [Some(1), Some(2)]
Option\none()->unzip(); // [None, None]
When to Use Option
- Repository lookups:
find_by_id()returnsOption<Entity>instead of?Entity - Configuration:
get_setting()returnsOption<string>-- absence is explicit - Chained lookups: Use
andThen()to chain fallible lookups without nested null checks - Safe data access: Parse user input with
from_nullable()and transform withmap()
See src/Psl/Option/ for the full API.