Issues (3)

src/PhpOption/Option.php (2 issues)

1
<?php
2
3
/*
4
 * Copyright 2012 Johannes M. Schmitt <[email protected]>
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace PhpOption;
20
21
use ArrayAccess;
22
use IteratorAggregate;
23
24
/**
25
 * @template T
26
 *
27
 * @implements IteratorAggregate<T>
28
 */
29
abstract class Option implements IteratorAggregate
30
{
31
    /**
32
     * Creates an option given a return value.
33
     *
34
     * This is intended for consuming existing APIs and allows you to easily
35
     * convert them to an option. By default, we treat ``null`` as the None
36
     * case, and everything else as Some.
37
     *
38
     * @template S
39
     *
40
     * @param S $value     The actual return value.
41
     * @param S $noneValue The value which should be considered "None"; null by
42
     *                     default.
43
     *
44
     * @return Option<S>
45
     */
46
    public static function fromValue($value, $noneValue = null)
47
    {
48
        if ($value === $noneValue) {
49
            return None::create();
50
        }
51
52
        return new Some($value);
53
    }
54
55
    /**
56
     * Creates an option from an array's value.
57
     *
58
     * If the key does not exist in the array, the array is not actually an
59
     * array, or the array's value at the given key is null, None is returned.
60
     * Otherwise, Some is returned wrapping the value at the given key.
61
     *
62
     * @template S
63
     *
64
     * @param array<string|int,S>|ArrayAccess<string|int,S>|null $array A potential array or \ArrayAccess value.
65
     * @param string                                             $key   The key to check.
66
     *
67
     * @return Option<S>
68
     */
69
    public static function fromArraysValue($array, $key)
70
    {
71
        if (!(is_array($array) || $array instanceof ArrayAccess) || !isset($array[$key])) {
0 ignored issues
show
$array is always a sub-type of ArrayAccess.
Loading history...
72
            return None::create();
73
        }
74
75
        return new Some($array[$key]);
76
    }
77
78
    /**
79
     * Creates a lazy-option with the given callback.
80
     *
81
     * This is also a helper constructor for lazy-consuming existing APIs where
82
     * the return value is not yet an option. By default, we treat ``null`` as
83
     * None case, and everything else as Some.
84
     *
85
     * @template S
86
     *
87
     * @param callable $callback  The callback to evaluate.
88
     * @param array    $arguments The arguments for the callback.
89
     * @param S        $noneValue The value which should be considered "None";
90
    *                             null by default.
91
     *
92
     * @return LazyOption<S>
93
     */
94
    public static function fromReturn($callback, array $arguments = [], $noneValue = null)
95
    {
96
        return new LazyOption(static function () use ($callback, $arguments, $noneValue) {
97
            /** @var mixed */
98
            $return = call_user_func_array($callback, $arguments);
99
100
            if ($return === $noneValue) {
101
                return None::create();
102
            }
103
104
            return new Some($return);
105
        });
106
    }
107
108
    /**
109
     * Option factory, which creates new option based on passed value.
110
     *
111
     * If value is already an option, it simply returns. If value is callable,
112
     * LazyOption with passed callback created and returned. If Option
113
     * returned from callback, it returns directly. On other case value passed
114
     * to Option::fromValue() method.
115
     *
116
     * @template S
117
     *
118
     * @param Option<S>|callable|S $value
119
     * @param S                    $noneValue Used when $value is mixed or
120
     *                                        callable, for None-check.
121
     *
122
     * @return Option<S>|LazyOption<S>
123
     */
124
    public static function ensure($value, $noneValue = null)
125
    {
126
        if ($value instanceof self) {
0 ignored issues
show
$value is always a sub-type of self.
Loading history...
127
            return $value;
128
        } elseif (is_callable($value)) {
129
            return new LazyOption(static function () use ($value, $noneValue) {
130
                /** @var mixed */
131
                $return = $value();
132
133
                if ($return instanceof self) {
134
                    return $return;
135
                } else {
136
                    return self::fromValue($return, $noneValue);
137
                }
138
            });
139
        } else {
140
            return self::fromValue($value, $noneValue);
141
        }
142
    }
143
144
    /**
145
     * Lift a function so that it accepts Option as parameters.
146
     *
147
     * We return a new closure that wraps the original callback. If any of the
148
     * parameters passed to the lifted function is empty, the function will
149
     * return a value of None. Otherwise, we will pass all parameters to the
150
     * original callback and return the value inside a new Option, unless an
151
     * Option is returned from the function, in which case, we use that.
152
     *
153
     * @template S
154
     *
155
     * @param callable $callback
156
     * @param mixed    $noneValue
157
     *
158
     * @return callable
159
     */
160
    public static function lift($callback, $noneValue = null)
161
    {
162
        return static function () use ($callback, $noneValue) {
163
            /** @var array<int, mixed> */
164
            $args = func_get_args();
165
166
            $reduced_args = array_reduce(
167
                $args,
168
                /** @param bool $status */
169
                static function ($status, self $o) {
170
                    return $o->isEmpty() ? true : $status;
171
                },
172
                false
173
            );
174
            // if at least one parameter is empty, return None
175
            if ($reduced_args) {
176
                return None::create();
177
            }
178
179
            $args = array_map(
180
                /** @return T */
181
                static function (self $o) {
182
                    // it is safe to do so because the fold above checked
183
                    // that all arguments are of type Some
184
                    /** @var T */
185
                    return $o->get();
186
                },
187
                $args
188
            );
189
190
            return self::ensure(call_user_func_array($callback, $args), $noneValue);
191
        };
192
    }
193
194
    /**
195
     * Returns the value if available, or throws an exception otherwise.
196
     *
197
     * @throws \RuntimeException If value is not available.
198
     *
199
     * @return T
200
     */
201
    abstract public function get();
202
203
    /**
204
     * Returns the value if available, or the default value if not.
205
     *
206
     * @template S
207
     *
208
     * @param S $default
209
     *
210
     * @return T|S
211
     */
212
    abstract public function getOrElse($default);
213
214
    /**
215
     * Returns the value if available, or the results of the callable.
216
     *
217
     * This is preferable over ``getOrElse`` if the computation of the default
218
     * value is expensive.
219
     *
220
     * @template S
221
     *
222
     * @param callable():S $callable
223
     *
224
     * @return T|S
225
     */
226
    abstract public function getOrCall($callable);
227
228
    /**
229
     * Returns the value if available, or throws the passed exception.
230
     *
231
     * @param \Exception $ex
232
     *
233
     * @return T
234
     */
235
    abstract public function getOrThrow(\Exception $ex);
236
237
    /**
238
     * Returns true if no value is available, false otherwise.
239
     *
240
     * @return bool
241
     */
242
    abstract public function isEmpty();
243
244
    /**
245
     * Returns true if a value is available, false otherwise.
246
     *
247
     * @return bool
248
     */
249
    abstract public function isDefined();
250
251
    /**
252
     * Returns this option if non-empty, or the passed option otherwise.
253
     *
254
     * This can be used to try multiple alternatives, and is especially useful
255
     * with lazy evaluating options:
256
     *
257
     * ```php
258
     *     $repo->findSomething()
259
     *         ->orElse(new LazyOption(array($repo, 'findSomethingElse')))
260
     *         ->orElse(new LazyOption(array($repo, 'createSomething')));
261
     * ```
262
     *
263
     * @param Option<T> $else
264
     *
265
     * @return Option<T>
266
     */
267
    abstract public function orElse(self $else);
268
269
    /**
270
     * This is similar to map() below except that the return value has no meaning;
271
     * the passed callable is simply executed if the option is non-empty, and
272
     * ignored if the option is empty.
273
     *
274
     * In all cases, the return value of the callable is discarded.
275
     *
276
     * ```php
277
     *     $comment->getMaybeFile()->ifDefined(function($file) {
278
     *         // Do something with $file here.
279
     *     });
280
     * ```
281
     *
282
     * If you're looking for something like ``ifEmpty``, you can use ``getOrCall``
283
     * and ``getOrElse`` in these cases.
284
     *
285
     * @deprecated Use forAll() instead.
286
     *
287
     * @param callable(T):mixed $callable
288
     *
289
     * @return void
290
     */
291
    abstract public function ifDefined($callable);
292
293
    /**
294
     * This is similar to map() except that the return value of the callable has no meaning.
295
     *
296
     * The passed callable is simply executed if the option is non-empty, and ignored if the
297
     * option is empty. This method is preferred for callables with side-effects, while map()
298
     * is intended for callables without side-effects.
299
     *
300
     * @param callable(T):mixed $callable
301
     *
302
     * @return Option<T>
303
     */
304
    abstract public function forAll($callable);
305
306
    /**
307
     * Applies the callable to the value of the option if it is non-empty,
308
     * and returns the return value of the callable wrapped in Some().
309
     *
310
     * If the option is empty, then the callable is not applied.
311
     *
312
     * ```php
313
     *     (new Some("foo"))->map('strtoupper')->get(); // "FOO"
314
     * ```
315
     *
316
     * @template S
317
     *
318
     * @param callable(T):S $callable
319
     *
320
     * @return Option<S>
321
     */
322
    abstract public function map($callable);
323
324
    /**
325
     * Applies the callable to the value of the option if it is non-empty, and
326
     * returns the return value of the callable directly.
327
     *
328
     * In contrast to ``map``, the return value of the callable is expected to
329
     * be an Option itself; it is not automatically wrapped in Some().
330
     *
331
     * @template S
332
     *
333
     * @param callable(T):Option<S> $callable must return an Option
334
     *
335
     * @return Option<S>
336
     */
337
    abstract public function flatMap($callable);
338
339
    /**
340
     * If the option is empty, it is returned immediately without applying the callable.
341
     *
342
     * If the option is non-empty, the callable is applied, and if it returns true,
343
     * the option itself is returned; otherwise, None is returned.
344
     *
345
     * @param callable(T):bool $callable
346
     *
347
     * @return Option<T>
348
     */
349
    abstract public function filter($callable);
350
351
    /**
352
     * If the option is empty, it is returned immediately without applying the callable.
353
     *
354
     * If the option is non-empty, the callable is applied, and if it returns false,
355
     * the option itself is returned; otherwise, None is returned.
356
     *
357
     * @param callable(T):bool $callable
358
     *
359
     * @return Option<T>
360
     */
361
    abstract public function filterNot($callable);
362
363
    /**
364
     * If the option is empty, it is returned immediately.
365
     *
366
     * If the option is non-empty, and its value does not equal the passed value
367
     * (via a shallow comparison ===), then None is returned. Otherwise, the
368
     * Option is returned.
369
     *
370
     * In other words, this will filter all but the passed value.
371
     *
372
     * @param T $value
373
     *
374
     * @return Option<T>
375
     */
376
    abstract public function select($value);
377
378
    /**
379
     * If the option is empty, it is returned immediately.
380
     *
381
     * If the option is non-empty, and its value does equal the passed value (via
382
     * a shallow comparison ===), then None is returned; otherwise, the Option is
383
     * returned.
384
     *
385
     * In other words, this will let all values through except the passed value.
386
     *
387
     * @param T $value
388
     *
389
     * @return Option<T>
390
     */
391
    abstract public function reject($value);
392
393
    /**
394
     * Binary operator for the initial value and the option's value.
395
     *
396
     * If empty, the initial value is returned. If non-empty, the callable
397
     * receives the initial value and the option's value as arguments.
398
     *
399
     * ```php
400
     *
401
     *     $some = new Some(5);
402
     *     $none = None::create();
403
     *     $result = $some->foldLeft(1, function($a, $b) { return $a + $b; }); // int(6)
404
     *     $result = $none->foldLeft(1, function($a, $b) { return $a + $b; }); // int(1)
405
     *
406
     *     // This can be used instead of something like the following:
407
     *     $option = Option::fromValue($integerOrNull);
408
     *     $result = 1;
409
     *     if ( ! $option->isEmpty()) {
410
     *         $result += $option->get();
411
     *     }
412
     * ```
413
     *
414
     * @template S
415
     *
416
     * @param S                $initialValue
417
     * @param callable(S, T):S $callable
418
     *
419
     * @return S
420
     */
421
    abstract public function foldLeft($initialValue, $callable);
422
423
    /**
424
     * foldLeft() but with reversed arguments for the callable.
425
     *
426
     * @template S
427
     *
428
     * @param S                $initialValue
429
     * @param callable(T, S):S $callable
430
     *
431
     * @return S
432
     */
433
    abstract public function foldRight($initialValue, $callable);
434
}
435