1: | <?php declare(strict_types=1); |
2: | |
3: | namespace TH\Maybe\Option; |
4: | |
5: | use TH\DocTest\Attributes\ExamplesSetup; |
6: | use TH\Maybe\Option; |
7: | use TH\Maybe\Result; |
8: | use TH\Maybe\Tests\Helpers\IgnoreUnusedResults; |
9: | |
10: | /** |
11: | * Return a `Option\Some` option containing `$value`. |
12: | * |
13: | * @template T |
14: | * @param T $value |
15: | * @return Option\Some<T> |
16: | */ |
17: | function some(mixed $value): Option\Some |
18: | { |
19: | return new Option\Some($value); |
20: | } |
21: | |
22: | /** |
23: | * Return a `Option\None` option containing no values. |
24: | */ |
25: | function none(): Option\None |
26: | { |
27: | return Option\None::instance; |
28: | } |
29: | |
30: | /** |
31: | * Transform a value into an `Option`. |
32: | * It will be a `Some` option containing `$value` if `$value` is different from `$noneValue` (default `null`) |
33: | * |
34: | * # Examples |
35: | * |
36: | * ``` |
37: | * self::assertEq(Option\fromValue("fruits"), Option\some("fruits")); |
38: | * self::assertEq(Option\fromValue(null), Option\none()); |
39: | * ``` |
40: | * |
41: | * @template U |
42: | * @param U $value |
43: | * @return Option<U> |
44: | */ |
45: | function fromValue(mixed $value, mixed $noneValue = null, bool $strict = true): Option |
46: | { |
47: | $same = $strict |
48: | ? ($value === $noneValue) |
49: | // @phpcs:ignore SlevomatCodingStandard.Operators.DisallowEqualOperators |
50: | : ($value == $noneValue); // @phpstan-ignore equal.notAllowed |
51: | |
52: | /** @var Option<U> */ |
53: | return $same |
54: | ? Option\none() |
55: | : Option\some($value); |
56: | } |
57: | |
58: | /** |
59: | * Execute a callable and transform the result into an `Option`. |
60: | * It will be a `Some` option containing the result if it is different from `$noneValue` (default `null`). |
61: | * |
62: | * # Examples |
63: | * |
64: | * Successful execution: |
65: | * |
66: | * ``` |
67: | * self::assertEq(Option\of(fn() => "fruits"), Option\some("fruits")); |
68: | * ``` |
69: | * |
70: | * Convertion of `null` to `Option\None`: |
71: | * |
72: | * ``` |
73: | * self::assertEq(Option\of(fn() => null), Option\none()); |
74: | * ``` |
75: | * |
76: | * @template U |
77: | * @param callable():U $callback |
78: | * @return Option<U> |
79: | */ |
80: | function of(callable $callback, mixed $noneValue = null, bool $strict = true): Option |
81: | { |
82: | return Option\fromValue($callback(), $noneValue, $strict); |
83: | } |
84: | |
85: | /** |
86: | * Execute a callable and transform the result into an `Option` as `Option\of()` does |
87: | * but also return `Option\None` if it an exception matching $exceptionClass was thrown. |
88: | * |
89: | * # Examples |
90: | * |
91: | * Successful execution: |
92: | * |
93: | * ``` |
94: | * self::assertEq(Option\tryOf(fn () => strtolower("FRUITS")), Option\some("fruits")); |
95: | * ``` |
96: | * |
97: | * Convertion of `null` to `Option\None`: |
98: | * |
99: | * ``` |
100: | * self::assertEq(Option\tryOf(fn() => null), Option\none()); |
101: | * ``` |
102: | * |
103: | * Checked Exception: |
104: | * |
105: | * ``` |
106: | * self::assertEq(Option\tryOf(fn () => new \DateTimeImmutable("nope")), Option\none()); |
107: | * ``` |
108: | * |
109: | * Unchecked Exception: |
110: | * |
111: | * ``` |
112: | * self::assertEq(Option\tryOf(fn () => 1 / 0), Option\none()); |
113: | * // @throws DivisionByZeroError Division by zero |
114: | * ``` |
115: | * |
116: | * @template U |
117: | * @template E of \Throwable |
118: | * @param callable():U $callback |
119: | * @param class-string<E> $exceptionClass |
120: | * @return Option<U> |
121: | * @throws \Throwable |
122: | */ |
123: | function tryOf( |
124: | callable $callback, |
125: | mixed $noneValue = null, |
126: | bool $strict = true, |
127: | string $exceptionClass = \Exception::class, |
128: | ): Option { |
129: | try { |
130: | return Option\of($callback, $noneValue, $strict); |
131: | } catch (\Throwable $th) { |
132: | if (\is_a($th, $exceptionClass)) { |
133: | return Option\none(); |
134: | } |
135: | |
136: | throw $th; |
137: | } |
138: | } |
139: | |
140: | /** |
141: | * Converts from `Option<Option<T>>` to `Option<T>`. |
142: | * |
143: | * # Examples |
144: | * |
145: | * ``` |
146: | * $x = Option\Some("vegetables"); |
147: | * self::assertSame(Option\flatten(Option\some($x)), $x); |
148: | * self::assertSame(Option\flatten(Option\some(Option\none())), Option\none()); |
149: | * self::assertSame(Option\flatten(Option\none()), Option\none()); |
150: | * ``` |
151: | * |
152: | * @template U |
153: | * @param Option<Option<U>> $option |
154: | * @return Option<U> |
155: | */ |
156: | function flatten(Option $option): Option |
157: | { |
158: | return $option instanceof Option\None |
159: | ? $option |
160: | /** @phpstan-ignore missingType.checkedException */ |
161: | : $option->unwrap(); |
162: | } |
163: | |
164: | /** |
165: | * Unzips an option containing a tuple of two options. |
166: | * |
167: | * If `self` is `Some([a, b])` this method returns `[Some(a), Some(b)]`. Otherwise, `[None, None]` is returned. |
168: | * |
169: | * ``` |
170: | * $x = Option\Some("vegetables"); |
171: | * self::assertEq(Option\unzip(Option\some(["a", 2])), [Option\some("a"), Option\some(2)]); |
172: | * self::assertSame(Option\unzip(Option\none()), [Option\none(), Option\none()]); |
173: | * ``` |
174: | * |
175: | * @template U |
176: | * @template V |
177: | * @param Option<array{U, V}> $option |
178: | * @return array{Option<U>, Option<V>} |
179: | */ |
180: | function unzip(Option $option): array |
181: | { |
182: | /** @var array{Option<U>, Option<V>} */ |
183: | return $option->mapOrElse( |
184: | static fn (array $a): array => [Option\some($a[0]), Option\some($a[1])], |
185: | static fn (): array => [Option\none(), Option\none()], |
186: | ); |
187: | } |
188: | |
189: | /** |
190: | * Transposes an `Option` of a `Result` into a `Result` of an `Option`. |
191: | * |
192: | * `None` will be mapped to `Ok(None)`. |
193: | * `Some(Ok(_))` and `Some(Err(_))` will be mapped to `Ok(Some(_))` and `Err(_)`. |
194: | * |
195: | * ``` |
196: | * use TH\Maybe\Result; |
197: | * |
198: | * self::assertEq(Result\ok(Option\some(4)), Option\transpose(Option\some(Result\ok(4)))); |
199: | * self::assertEq(Result\err("meat"), Option\transpose(Option\some(Result\err("meat")))); |
200: | * self::assertEq(Option\transpose(Option\none()), Result\ok(Option\none())); |
201: | * ``` |
202: | * |
203: | * @template U |
204: | * @template E |
205: | * @param Option<Result<U, E>> $option |
206: | * @return Result<Option<U>, E> |
207: | */ |
208: | #[ExamplesSetup(IgnoreUnusedResults::class)] |
209: | function transpose(Option $option): Result |
210: | { |
211: | /** @var Result<Option<U>, E> */ |
212: | return $option->mapOrElse( // @phpstan-ignore varTag.type |
213: | // @phpstan-ignore-next-line |
214: | static fn (Result $result) => $result->map(Option\some(...)), |
215: | static fn () => Result\ok(Option\none()), |
216: | ); |
217: | } |
218: |