Rust-style Option and Result Classes for PHP
at main 262 lines 5.8 kB view raw
1<?php 2 3namespace Ciarancoza\OptionResult; 4 5use Ciarancoza\OptionResult\Exceptions\UnwrapErrException; 6use Ciarancoza\OptionResult\Exceptions\UnwrapOkException; 7 8/** 9 * Result<T, E> represents a success (`ok`) or an error (`err`) 10 * 11 * @template T 12 * @template E 13 */ 14class Result 15{ 16 /** 17 * Creates an `ok` result 18 * 19 * @param T $value 20 * @return Result<T,never> 21 */ 22 public static function Ok(mixed $value = true): static 23 { 24 return new static($value, true); 25 } 26 27 /** 28 * Creates an `err` result 29 * 30 * @param E $value 31 * @return Result<never,E> 32 */ 33 public static function Err(mixed $value): static 34 { 35 return new static($value, false); 36 } 37 38 /** @param T $value */ 39 private function __construct( 40 protected mixed $value, 41 protected bool $isOk, 42 ) {} 43 44 /** Returns `true` if the result is an `ok` result. */ 45 public function isOk(): bool 46 { 47 return $this->isOk; 48 } 49 50 /** Returns `true` if the result is an `err` result. */ 51 public function isErr(): bool 52 { 53 return ! $this->isOk(); 54 } 55 56 /** 57 * Returns `$and` if `ok`, otherwise returns the current `err` 58 * 59 * @template V 60 * 61 * @param Result<V,E> $and 62 * @return Result<V,E> 63 */ 64 public function and(self $and): Result 65 { 66 if ($this->isErr()) { 67 return Result::Err($this->unwrapErr()); 68 } 69 70 return $and; 71 } 72 73 /** 74 * Calls `$then` on contained value if `ok`, otherwise returns the current `err` 75 * 76 * @template U 77 * 78 * @param callable(T): Result<U,E> $then Function to transform the value 79 * @return Result<U,E> 80 */ 81 public function andThen(callable $then): Result 82 { 83 if ($this->isErr()) { 84 return Result::Err($this->unwrapErr()); 85 } 86 87 return $then($this->unwrap()); 88 } 89 90 /** 91 * Throws UnwrapErrException with a custom message if `err`, otherwise returns the inner value 92 * 93 * @return T 94 * 95 * @throws UnwrapErrException 96 */ 97 public function expect(string $msg): mixed 98 { 99 if ($this->isErr()) { 100 throw new UnwrapErrException($msg); 101 } 102 103 return $this->unwrap(); 104 } 105 106 /** 107 * Throws UnwrapOkException with a custom message if `ok`, otherwise returns the inner error value 108 * 109 * @return E 110 * 111 * @throws UnwrapOkException 112 */ 113 public function expectErr(string $msg): mixed 114 { 115 if ($this->isOk()) { 116 throw new UnwrapOkException($msg); 117 } 118 119 return $this->unwrapErr(); 120 } 121 122 /** 123 * Returns `Some(T)` if `ok`, or `None` if `err` 124 * 125 * @return Option<T> 126 */ 127 public function getOk(): Option 128 { 129 if ($this->isErr()) { 130 return Option::None(); 131 } 132 133 return Option::Some($this->value); 134 } 135 136 /** 137 * Returns `Some(E)` if `err`, or `None` if `ok` 138 * 139 * @return Option<E> 140 */ 141 public function getErr(): Option 142 { 143 if ($this->isOk()) { 144 return Option::None(); 145 } 146 147 return Option::Some($this->value); 148 } 149 150 /** 151 * Returns the contained value if `ok`, otherwise throws UnwrapErrException 152 * 153 * @return T The contained value 154 * 155 * @throws UnwrapErrException 156 */ 157 public function unwrap(): mixed 158 { 159 if ($this->isErr()) { 160 throw new UnwrapErrException; 161 } 162 163 return $this->value; 164 } 165 166 /** 167 * Returns the contained value if `err`, otherwise throws UnwrapOkException 168 * 169 * @return E The contained error value 170 * 171 * @throws UnwrapOkException 172 */ 173 public function unwrapErr(): mixed 174 { 175 if ($this->isOk()) { 176 throw new UnwrapOkException; 177 } 178 179 return $this->value; 180 } 181 182 /** 183 * Returns the contained `ok` value or a provided default. 184 * 185 * @param V $or 186 * @return T|V 187 */ 188 public function unwrapOr(mixed $or): mixed 189 { 190 if ($this->isOk()) { 191 return $this->unwrap(); 192 } 193 194 return is_callable($or) ? $or() : $or; 195 } 196 197 /** 198 * Returns the contained `ok` value or computes from closure with error value 199 * 200 * @param callable(E): V $fn 201 * @return T|V 202 */ 203 public function unwrapOrElse(callable $fn): mixed 204 { 205 if ($this->isOk()) { 206 return $this->unwrap(); 207 } 208 209 return $fn($this->value); 210 } 211 212 /** 213 * If `ok`, transform the value with `$fn` 214 * 215 * @template U 216 * 217 * @param callable(T): U $fn Function to transform the value 218 * @return Result<U,E> 219 */ 220 public function map(callable $fn): Result 221 { 222 if ($this->isErr()) { 223 return Result::Err($this->value); 224 } 225 226 return Result::Ok($fn($this->value)); 227 } 228 229 /** 230 * Calls `fn` on a contained value if `ok`, or returns $or if `err` 231 * 232 * @template V $or 233 * @template U 234 * 235 * @param callable(T): U $fn Function to transform the value 236 * @return V|U 237 */ 238 public function mapOr(mixed $or, callable $fn): mixed 239 { 240 return match (true) { 241 $this->isOk() => $fn($this->unwrap()), 242 $this->isErr() => is_callable($or) ? $or() : $or, 243 }; 244 } 245 246 /** 247 * If `err`, transform the error value with `$fn` 248 * 249 * @template U 250 * 251 * @param callable(E): U $fn Function to transform the value 252 * @return Result<T,U> 253 */ 254 public function mapErr(callable $fn): Result 255 { 256 if ($this->isOk()) { 257 return Result::Ok($this->value); 258 } 259 260 return Result::Err($fn($this->unwrapErr())); 261 } 262}