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: