├── src ├── Exception │ ├── LogicOptionException.php │ └── RuntimeOptionException.php ├── Option │ └── functions.php ├── OptionFactory.php ├── None.php ├── Some.php └── Option.php ├── .php-cs-fixer.php ├── composer.json ├── phpunit.xml ├── LICENSE └── README.md /src/Exception/LogicOptionException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Std\Type\Exception; 15 | 16 | class LogicOptionException extends \LogicException 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/RuntimeOptionException.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Std\Type\Exception; 15 | 16 | class RuntimeOptionException extends \RuntimeException 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | For the full copyright and license information, please view the LICENSE 9 | file that was distributed with this source code. 10 | EOF; 11 | 12 | $finder = (new PhpCsFixer\Finder()) 13 | ->in(__DIR__.'/src') 14 | ; 15 | 16 | return (new PhpCsFixer\Config()) 17 | ->setRules([ 18 | '@Symfony' => true, 19 | 'header_comment' => ['header' => $header], 20 | 'declare_strict_types' => true, 21 | ]) 22 | ->setFinder($finder) 23 | ; 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yceruto/option-type", 3 | "description": "An Option type that represents an optional value", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Yonel Ceruto", 9 | "email": "open@yceruto.dev" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=8.2" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "^11.1", 17 | "phpstan/phpstan": "^1.11", 18 | "friendsofphp/php-cs-fixer": "^3.54" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Std\\Type\\": "src/" 23 | }, 24 | "files": ["src/Option/functions.php"] 25 | }, 26 | "autoload-dev": { 27 | "psr-4": { 28 | "Std\\Type\\Tests\\": "tests/" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | 20 | src 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Option/functions.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Std\Type\Option; 15 | 16 | use Std\Type\None; 17 | use Std\Type\Some; 18 | 19 | if (!\function_exists('some')) { 20 | /** 21 | * Some value. 22 | * 23 | * @template T 24 | * 25 | * @param T $value A value of type T 26 | * 27 | * @return Some Some option 28 | */ 29 | function some(mixed $value): Some 30 | { 31 | return new Some($value); 32 | } 33 | } 34 | 35 | if (!\function_exists('none')) { 36 | /** 37 | * No value. 38 | */ 39 | function none(): None 40 | { 41 | return new None(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present Yonel Ceruto González 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/OptionFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Std\Type; 15 | 16 | final readonly class OptionFactory 17 | { 18 | /** 19 | * Returns an `Option` with the specified value. 20 | * 21 | * If the value is `null`, returns `None`, otherwise returns `Some`. 22 | * 23 | * Examples 24 | * ``` 25 | * $x = Option::from(2); 26 | * assert($x->isSome(), 'Expected $x to be Some.'); 27 | * 28 | * $x = Option::from(null); 29 | * assert($x->isNone(), 'Expected $x to be None.'); 30 | * ``` 31 | * 32 | * @template T of mixed 33 | * 34 | * @param T $value A value of type T 35 | * 36 | * @return None|Some Some or None Option 37 | */ 38 | public static function from(mixed $value): None|Some 39 | { 40 | return null === $value ? new None() : new Some($value); 41 | } 42 | 43 | private function __construct() 44 | { 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/None.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Std\Type; 15 | 16 | use Std\Type\Exception\RuntimeOptionException; 17 | 18 | /** 19 | * No value. 20 | * 21 | * Also see {@see none()} for a shorter way to create a None Option. 22 | * 23 | * @implements Option 24 | */ 25 | final readonly class None implements Option 26 | { 27 | public function isSome(): bool 28 | { 29 | return false; 30 | } 31 | 32 | public function isNone(): bool 33 | { 34 | return true; 35 | } 36 | 37 | public function match(callable $some, callable $none): mixed 38 | { 39 | return $none(); 40 | } 41 | 42 | public function expect(string $message): mixed 43 | { 44 | throw new RuntimeOptionException($message); 45 | } 46 | 47 | public function unwrap(): mixed 48 | { 49 | throw new RuntimeOptionException(sprintf('Calling unwrap() method on a %s option. Check isNone() method first or use a fallback method instead.', self::class)); 50 | } 51 | 52 | public function unwrapOr(mixed $default): mixed 53 | { 54 | return $default; 55 | } 56 | 57 | public function unwrapOrElse(callable $fn): mixed 58 | { 59 | return $fn(); 60 | } 61 | 62 | public function unwrapOrThrow(\Throwable $error): mixed 63 | { 64 | throw $error; 65 | } 66 | 67 | public function map(callable $fn): self 68 | { 69 | return $this; 70 | } 71 | 72 | public function mapOr(callable $fn, mixed $default): mixed 73 | { 74 | return $default; 75 | } 76 | 77 | public function mapOrElse(callable $fn, callable $default): mixed 78 | { 79 | return $default(); 80 | } 81 | 82 | public function or(Option $option): Option 83 | { 84 | return $option; 85 | } 86 | 87 | public function orElse(callable $fn): Option 88 | { 89 | return $fn(); 90 | } 91 | 92 | public function xor(Option $option): Option 93 | { 94 | return $option instanceof self ? $this : $option; 95 | } 96 | 97 | public function and(Option $option): Option 98 | { 99 | return $this; 100 | } 101 | 102 | public function andThen(callable $fn): self 103 | { 104 | return $this; 105 | } 106 | 107 | public function iterate(): iterable 108 | { 109 | return []; 110 | } 111 | 112 | public function filter(callable $predicate): self 113 | { 114 | return $this; 115 | } 116 | 117 | public function equals(Option $option): bool 118 | { 119 | return $option instanceof self; 120 | } 121 | 122 | public function flatten(): self 123 | { 124 | return $this; 125 | } 126 | 127 | public function clone(): self 128 | { 129 | return clone $this; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Option type 2 | 3 | [![Latest Stable Version](https://poser.pugx.org/yceruto/option-type/v?v=1)](https://packagist.org/packages/yceruto/option-type) 4 | [![Unstable](http://poser.pugx.org/yceruto/option-type/v/unstable)](https://packagist.org/packages/yceruto/option-type) 5 | [![License](https://poser.pugx.org/yceruto/option-type/license)](https://packagist.org/packages/yceruto/option-type) 6 | [![PHP Version Require](https://poser.pugx.org/yceruto/option-type/require/php)](https://packagist.org/packages/yceruto/option-type) 7 | 8 | The `Option` type represents a value that might or might not be there. It's all about 9 | null safety in PHP! 10 | 11 | > [!NOTE] 12 | > Inspired by [Rust's Option type](https://doc.rust-lang.org/std/option/) and other 13 | > languages like Scala, Swift, F#, etc. 14 | 15 | ## Installation 16 | 17 | ```bash 18 | composer require yceruto/option-type 19 | ``` 20 | 21 | ## Handling the presence or absence of a value with `null` 22 | 23 | In PHP, denoting the absence of a value is done with `null`, e.g. when a `divide` 24 | function returns `null` if the divisor is `0`. 25 | 26 | ```php 27 | function divide(int $dividend, int $divisor): ?int 28 | { 29 | if (0 === $divisor) { 30 | return null; 31 | } 32 | 33 | return intdiv($dividend, $divisor); 34 | } 35 | 36 | function success(int $result): string { 37 | return sprintf('Result: %d', $result); 38 | } 39 | 40 | $result = divide(10, 2); 41 | 42 | echo success($result); 43 | ``` 44 | 45 | Can you spot the issue in this code? Apparently, everything is fine until you try to 46 | divide by zero. The function will return `null`, and the `success()` function will throw 47 | a `TypeError` because it expects an `int` value, not `null`. 48 | 49 | The issue with this approach is that it's too easy to overlook checking if the value is 50 | `null`, leading to runtime errors, and this is where the `Option` type comes in handy: it 51 | always forces you to deal with the `null` case. 52 | 53 | ## Handling the presence or absence of a value with `Option` 54 | 55 | Options often work with pattern matching to check if there’s a value and act accordingly, 56 | always making sure to handle the `null` case. 57 | 58 | ```php 59 | use Std\Type\Option; 60 | use function Std\Type\Option\none; 61 | use function Std\Type\Option\some; 62 | 63 | /** 64 | * @return Option 65 | */ 66 | function divide(int $dividend, int $divisor): Option 67 | { 68 | if (0 === $divisor) { 69 | return none(); 70 | } 71 | 72 | return some(intdiv($dividend, $divisor)); 73 | } 74 | 75 | function success(int $result): string { 76 | return sprintf('Result: %d', $result); 77 | } 78 | 79 | // The return value of the function is an Option 80 | $result = divide(10, 2); 81 | 82 | // Pattern match to retrieve the value 83 | echo $result->match( 84 | // The division was valid 85 | some: fn (int $v) => success($v), 86 | // The division was invalid 87 | none: fn () => 'Division by zero!', 88 | ); 89 | ``` 90 | 91 | > [!TIP] 92 | >You can use the functions `some()` and `none()` as quick ways to create an `Option` 93 | >instance. `some()` is just like `new Some()`, meaning it includes a value, while 94 | >`none()` is the same as `new None()`, indicating it is missing a value. 95 | 96 | ## Documentation 97 | 98 | * [API Reference](docs/api_reference.md) 99 | * [Examples](docs/examples.md) 100 | 101 | ## License 102 | 103 | This software is published under the [MIT License](LICENSE) 104 | -------------------------------------------------------------------------------- /src/Some.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Std\Type; 15 | 16 | use Std\Type\Exception\LogicOptionException; 17 | 18 | /** 19 | * Some value. 20 | * 21 | * Also see {@see some()} for a shorter way to create a Some Option. 22 | * 23 | * @template T 24 | * 25 | * @implements Option 26 | */ 27 | final readonly class Some implements Option 28 | { 29 | private mixed $value; 30 | 31 | /** 32 | * @param T $value A value of type T 33 | */ 34 | public function __construct(mixed $value) 35 | { 36 | if (null === $value) { 37 | throw new LogicOptionException(sprintf('Cannot create %s option with a null value, use %s instead.', self::class, None::class)); 38 | } 39 | 40 | $this->value = $value; 41 | } 42 | 43 | public function isSome(): bool 44 | { 45 | return true; 46 | } 47 | 48 | public function isNone(): bool 49 | { 50 | return false; 51 | } 52 | 53 | public function match(callable $some, callable $none): mixed 54 | { 55 | return $some($this->value); 56 | } 57 | 58 | public function expect(string $message): mixed 59 | { 60 | return $this->value; 61 | } 62 | 63 | public function unwrap(): mixed 64 | { 65 | return $this->value; 66 | } 67 | 68 | public function unwrapOr(mixed $default): mixed 69 | { 70 | return $this->value; 71 | } 72 | 73 | public function unwrapOrElse(callable $fn): mixed 74 | { 75 | return $this->value; 76 | } 77 | 78 | public function unwrapOrThrow(\Throwable $error): mixed 79 | { 80 | return $this->value; 81 | } 82 | 83 | public function map(callable $fn): Option 84 | { 85 | if (null === $value = $fn($this->value)) { 86 | return new None(); 87 | } 88 | 89 | return new self($value); 90 | } 91 | 92 | public function mapOr(callable $fn, mixed $default): mixed 93 | { 94 | return $fn($this->value); 95 | } 96 | 97 | public function mapOrElse(callable $fn, callable $default): mixed 98 | { 99 | return $fn($this->value); 100 | } 101 | 102 | /** 103 | * @return Some 104 | */ 105 | public function or(Option $option): self 106 | { 107 | return $this; 108 | } 109 | 110 | /** 111 | * @return Some 112 | */ 113 | public function orElse(callable $fn): self 114 | { 115 | return $this; 116 | } 117 | 118 | public function xor(Option $option): Option 119 | { 120 | return $option instanceof self ? new None() : $this; 121 | } 122 | 123 | public function and(Option $option): Option 124 | { 125 | return $option; 126 | } 127 | 128 | public function andThen(callable $fn): Option 129 | { 130 | return $fn($this->value); 131 | } 132 | 133 | public function iterate(): iterable 134 | { 135 | return new \ArrayIterator((array) $this->value); 136 | } 137 | 138 | /** 139 | * @return None|Some 140 | */ 141 | public function filter(callable $predicate): Option 142 | { 143 | if ($predicate($this->value)) { 144 | return $this; 145 | } 146 | 147 | return new None(); 148 | } 149 | 150 | public function equals(Option $option): bool 151 | { 152 | return $option instanceof self && $this->value === $option->value; 153 | } 154 | 155 | public function flatten(): Option 156 | { 157 | if ($this->value instanceof Option) { 158 | return $this->value; 159 | } 160 | 161 | throw new LogicOptionException(sprintf('Calling %s() method on a non-Option value. Unexpected "%s" type.', __METHOD__, get_debug_type($this->value))); 162 | } 163 | 164 | /** 165 | * @return Some 166 | */ 167 | public function clone(): self 168 | { 169 | return clone $this; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/Option.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Std\Type; 15 | 16 | use Std\Type\Exception\LogicOptionException; 17 | use Std\Type\Exception\RuntimeOptionException; 18 | 19 | use function Std\Type\Option\some; 20 | 21 | /** 22 | * The Option type represents an optional value: every Option 23 | * is either Some and contains a value, or None, and does not. 24 | * 25 | * @template T 26 | */ 27 | interface Option 28 | { 29 | /** 30 | * Returns `true` if the option is a {@see Some} value. 31 | * 32 | * Examples 33 | * ``` 34 | * $x = some(2); 35 | * assert($x->isSome(), 'Expected $x to be Some.'); 36 | * 37 | * $x = none(); 38 | * assert(!$x->isSome(), 'Expected $x not to be Some.'); 39 | * ``` 40 | * 41 | * @return bool `true` if the option is a {@see Some} value, otherwise `false` 42 | */ 43 | public function isSome(): bool; 44 | 45 | /** 46 | * Returns `true` if the option is {@see None} value. 47 | * 48 | * Examples 49 | * ``` 50 | * $x = some(2); 51 | * assert(!$x->isNone(), 'Expected $x not to be None.'); 52 | * 53 | * $x = none(); 54 | * assert($x->isNone(), 'Expected $x to be None.'); 55 | * ``` 56 | * 57 | * @return bool `true` if the option is {@see None} value, otherwise `false` 58 | */ 59 | public function isNone(): bool; 60 | 61 | /** 62 | * Matches the option with the provided callables and returns the result. 63 | * 64 | * @template U 65 | * @template V 66 | * 67 | * @param callable(T): U $some A callable that returns a value of type U 68 | * @param callable(): V $none A callable that returns a value of type V 69 | * 70 | * @return U|V The result of the callable `$some` function if the option is {@see Some}, 71 | * otherwise the result of the callable `$none` function 72 | */ 73 | public function match(callable $some, callable $none): mixed; 74 | 75 | /** 76 | * Returns the contained {@see Some} value, or throws an exception with custom message if the value is {@see None}. 77 | * 78 | * Examples 79 | * ``` 80 | * $x = some(2); 81 | * assert(2 === $x->expect('A number must be provided.'), 'Expected $x to be 2.'); 82 | * 83 | * $x = none(); 84 | * $x->expect('A number.'); // throws RuntimeOptionException 85 | * ``` 86 | * 87 | * @param string $message A custom error message to use in the RuntimeOptionException 88 | * 89 | * @return T The contained value 90 | * 91 | * @throws RuntimeOptionException If the value is {@see None} with a custom error message provided. 92 | * We recommend that `expect()` messages are used to describe the reason 93 | * you expect the `Option` should be {@see Some}. 94 | */ 95 | public function expect(string $message): mixed; 96 | 97 | /** 98 | * Returns the contained {@see Some} value, or throws an exception if the value is {@see None}. 99 | * 100 | * Examples 101 | * ``` 102 | * $x = some(2); 103 | * assert(2 === $x->unwrap(), 'Expected $x to be 2.'); 104 | * 105 | * $x = none(); 106 | * $x->unwrap(); // throws LogicOptionException 107 | * ``` 108 | * 109 | * @return T The contained value 110 | * 111 | * @throws RuntimeOptionException If the value is {@see None} 112 | */ 113 | public function unwrap(): mixed; 114 | 115 | /** 116 | * Returns the contained {@see Some} value or a provided default. 117 | * 118 | * Examples 119 | * ``` 120 | * $x = some(2); 121 | * assert(2 === $x->unwrapOr(1), 'Expected $x to be 2.'); 122 | * 123 | * $x = none(); 124 | * assert(1 === $x->unwrapOr(1), 'Expected $x to be 1.'); 125 | * ``` 126 | * 127 | * @template U of T 128 | * 129 | * @param U $default A default value of type T 130 | * 131 | * @return T|U The contained value or the default 132 | */ 133 | public function unwrapOr(mixed $default): mixed; 134 | 135 | /** 136 | * Returns the contained {@see Some} value or computes it from a closure. 137 | * 138 | * Examples 139 | * ``` 140 | * $x = some(2); 141 | * assert(2 === $x->unwrapOrElse(fn () => 1), 'Expected $x to be 2.'); 142 | * 143 | * $x = none(); 144 | * assert(1 === $x->unwrapOrElse(fn () => 1), 'Expected $x to be 1.'); 145 | * ``` 146 | * 147 | * @template U 148 | * 149 | * @param callable(): U $fn A callable that returns a value of type U 150 | * 151 | * @return T|U The contained value or the result of the callable 152 | */ 153 | public function unwrapOrElse(callable $fn): mixed; 154 | 155 | /** 156 | * Returns the contained {@see Some} value or throws an exception. 157 | * 158 | * Examples 159 | * ``` 160 | * $x = some(2); 161 | * assert(2 === $x->unwrapOrThrow(), 'Expected $x to be 2.'); 162 | * 163 | * $x = none(); 164 | * $x->unwrapOrThrow(new UnknownNumberError()); // throws UnknownNumberError 165 | * ``` 166 | * 167 | * @return T The contained value 168 | * 169 | * @throws \Throwable If the value is {@see None} 170 | */ 171 | public function unwrapOrThrow(\Throwable $error): mixed; 172 | 173 | /** 174 | * Maps an `Option` to `Option` by applying a function to a contained value (if `Some`) 175 | * or returns `None` (if `None`). 176 | * 177 | * @template U 178 | * 179 | * @param callable(T): U $fn A callable that returns a value of type U 180 | * 181 | * @return self|self The mapped Option 182 | */ 183 | public function map(callable $fn): self; 184 | 185 | /** 186 | * Returns the provided default result (if `None`), 187 | * or applies a function to the contained value (if `Some`). 188 | * 189 | * Examples 190 | * ``` 191 | * $x = some(2); 192 | * assert(4 === $x->mapOr(fn ($value) => $value * 2, 0), 'Expected $x to be 4.'); 193 | * 194 | * $x = none(); 195 | * assert(0 === $x->mapOr(fn ($value) => $value * 2, 0), 'Expected $x to be 0.'); 196 | * ``` 197 | * 198 | * @template U 199 | * 200 | * @param callable(T): U $fn A callable that returns a value of type U 201 | * @param U $default A default value of type U 202 | * 203 | * @return T|U The contained value or the default 204 | */ 205 | public function mapOr(callable $fn, mixed $default): mixed; 206 | 207 | /** 208 | * Computes a default function result (if `None`), or 209 | * applies a different function to the contained value (if any). 210 | * 211 | * Examples 212 | * ``` 213 | * $x = some(2); 214 | * assert(4 === $x->mapOrElse(fn ($value) => $value * 2, fn () => 0), 'Expected $x to be 4.'); 215 | * 216 | * $x = none(); 217 | * assert(0 === $x->mapOrElse(fn ($value) => $value * 2, fn () => 0), 'Expected $x to be 0.'); 218 | * ``` 219 | * 220 | * @template U 221 | * 222 | * @param callable(T): U $fn A callable that returns a value of type U 223 | * @param callable(): U $default A callable that returns a default value of type U 224 | * 225 | * @return U The result of the callable 226 | */ 227 | public function mapOrElse(callable $fn, callable $default): mixed; 228 | 229 | /** 230 | * Returns the option if it contains a value, otherwise returns `$option`. 231 | * 232 | * Examples 233 | * ``` 234 | * $x = some(2); 235 | * $y = some(3); 236 | * assert($x->or($y)->isSome(), 'Expected $x to be Some.'); 237 | * 238 | * $x = none(); 239 | * $y = some(3); 240 | * assert($x->or($y)->isSome(), 'Expected $x to be Some.'); 241 | * 242 | * $x = none(); 243 | * $y = none(); 244 | * assert($x->or($y)->isNone(), 'Expected $x to be None.'); 245 | * ``` 246 | * 247 | * @param self $option The option to return if the original is {@see None} 248 | * 249 | * @return self The original option if it contains a value, otherwise `$option` 250 | */ 251 | public function or(self $option): self; 252 | 253 | /** 254 | * Returns the option if it contains a value, otherwise calls `$fn` and 255 | * returns the result. 256 | * 257 | * Examples 258 | * ``` 259 | * $x = some(2); 260 | * $y = some(3); 261 | * assert($x->orElse(fn () => $y)->isSome(), 'Expected $x to be Some.'); 262 | * 263 | * $x = none(); 264 | * $y = some(3); 265 | * assert($x->orElse(fn () => $y)->isSome(), 'Expected $x to be Some.'); 266 | * 267 | * $x = none(); 268 | * $y = none(); 269 | * assert($x->orElse(fn () => $y)->isNone(), 'Expected $x to be None.'); 270 | * ``` 271 | * 272 | * @param callable(): self $fn A callable that returns an Option 273 | * 274 | * @return self The original option if it contains a value, otherwise the result of the callable 275 | */ 276 | public function orElse(callable $fn): self; 277 | 278 | /** 279 | * Returns {@see Some} if exactly one of `$this`, `$option` is {@see Some}, otherwise returns {@see None}. 280 | * 281 | * Examples 282 | * ``` 283 | * $x = some(2); 284 | * $y = some(3); 285 | * assert($x->xor($y)->isNone(), 'Expected $x to be None.'); 286 | * 287 | * $x = none(); 288 | * $y = some(3); 289 | * assert($x->xor($y)->isSome(), 'Expected $x to be Some.'); 290 | * 291 | * $x = none(); 292 | * $y = none(); 293 | * assert($x->xor($y)->isNone(), 'Expected $x to be None.'); 294 | * ``` 295 | * 296 | * @param self $option The option to compare with 297 | * 298 | * @return self|self 299 | */ 300 | public function xor(self $option): self; 301 | 302 | /** 303 | * Returns {@see None} if the option is {@see None}, otherwise returns `$option`. 304 | * 305 | * Examples 306 | * ``` 307 | * $x = some(2); 308 | * $y = some(3); 309 | * assert($x->and($y)->isSome(), 'Expected $x and $y to be Some.'); 310 | * 311 | * $x = none(); 312 | * $y = some(3); 313 | * assert($x->and($y)->isNone(), 'Expected $x to be None.'); 314 | * 315 | * $x = none(); 316 | * $y = none(); 317 | * assert($x->and($y)->isNone(), 'Expected $x and $y to be None.'); 318 | * ``` 319 | * 320 | * @param self $option The option to compare with 321 | * 322 | * @return self|self `$option` if the original is {@see None}, otherwise the original option 323 | */ 324 | public function and(self $option): self; 325 | 326 | /** 327 | * Returns {@see None} if the Option is {@see None}, otherwise calls `$fn` with 328 | * the wrapped value and returns the result. 329 | * 330 | * Some languages call this method flatmap. 331 | * 332 | * Examples 333 | * ``` 334 | * $x = some(2)->andThen(fn ($value) => some($value * 2)); 335 | * assert(4 === $x->unwrap(), 'Expected $x to be 4.'); 336 | * 337 | * $x = none()->andThen(fn ($value) => some($value * 2)); 338 | * assert($x->isNone(), 'Expected $x to be None.'); 339 | * ``` 340 | * 341 | * @template U 342 | * 343 | * @param callable(T): self $fn A callable that returns an Option 344 | * 345 | * @return self|self The result of the callable 346 | */ 347 | public function andThen(callable $fn): self; 348 | 349 | /** 350 | * Returns an iterator over the possibly contained value. 351 | * 352 | * Examples 353 | * ``` 354 | * $x = some(2); 355 | * assert([2] === iterator_to_array($x->iterate()), 'Expected to be [2].'); 356 | * 357 | * $x = none(); 358 | * assert([] === iterator_to_array($x->iterate()), 'Expected to be [].'); 359 | * ``` 360 | * 361 | * @return iterable An iterator over the possibly contained value 362 | */ 363 | public function iterate(): iterable; 364 | 365 | /** 366 | * Returns {@see None} if the Option is {@see None}, otherwise calls `$predicate` 367 | * with the wrapped value and returns: 368 | * 369 | * - {@see Some}(v) If `$predicate` returns `true` (where `v` is the wrapped value), and 370 | * - {@see None} if `$predicate` returns `false`. 371 | * 372 | * Examples 373 | * ``` 374 | * $isEven = fn (int $value): bool => 0 === $value % 2; 375 | * 376 | * assert(none()->filter($isEven)->isNone(), 'Expected to be true.'); 377 | * assert(some(3)->filter($isEven)->isNone(), 'Expected to be true.'); 378 | * assert(some(2)->filter($isEven)->isSome(), 'Expected to be true.'); 379 | * ``` 380 | * 381 | * @param callable(T): bool $predicate A callable that returns a boolean 382 | * 383 | * @return self {@see Some} if it's {@see Some} option and the predicate is `true`, otherwise {@see None} 384 | */ 385 | public function filter(callable $predicate): self; 386 | 387 | /** 388 | * Compares the option with the provided option and returns `true` if they are equal. 389 | * 390 | * Examples 391 | * ``` 392 | * $x = some(2); 393 | * $y = some(2); 394 | * assert($x->equals($y), 'Expected $x to be equal to $y.'); 395 | * 396 | * $x = some(2); 397 | * $y = some(3); 398 | * assert(!$x->equals($y), 'Expected $x not to be equal to $y.'); 399 | * 400 | * $x = none(); 401 | * $y = none(); 402 | * assert($x->equals($y), 'Expected $x to be equal to $y.'); 403 | * ``` 404 | * 405 | * @param self $option The option to compare with 406 | * 407 | * @return bool `true` if the wrapped value is the same value as `$option`, otherwise `false` 408 | */ 409 | public function equals(self $option): bool; 410 | 411 | /** 412 | * Converts from `Option>` to `Option`. 413 | * 414 | * Examples 415 | * ``` 416 | * $x = some(some(2)); 417 | * assert(2 === $x->flatten()->unwrap(), 'Expected to be 2.'); 418 | * 419 | * $x = some(none()); 420 | * assert($x->flatten()->isNone(), 'Expected to be None.'); 421 | * 422 | * $x = none(); 423 | * assert($x->flatten()->isNone(), 'Expected to be None.'); 424 | * 425 | * # Flattening only removes one level of nesting at a time: 426 | * $x = some(some(some(2))); 427 | * assert(some(some(2)) == $x->flatten(), 'Expected to be Option>.'); 428 | * assert(some(2) == $x->flatten()->flatten(), 'Expected to be Option.'); 429 | * ``` 430 | * 431 | * @return self The flattened Option 432 | * 433 | * @throws LogicOptionException If the value is not an Option 434 | */ 435 | public function flatten(): self; 436 | 437 | /** 438 | * Returns a copy of the option. 439 | * 440 | * @return self A copy of the option 441 | */ 442 | public function clone(): self; 443 | } 444 | --------------------------------------------------------------------------------