Completed
Push — master ( b367d7...03bf23 )
by Marco
14s queued 11s
created

Optional::newEmpty()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 2
nc 2
nop 0
crap 2
1
<?php
2
/*
3
 * Oracle designates this
4
 * particular file as subject to the "Classpath" exception as provided
5
 * by Oracle in the LICENSE file that accompanied this code.
6
 */
7
8
declare(strict_types=1);
9
10
namespace ocramius\util;
11
12
use Exception;
13
use ocramius\util\exception\NoSuchElementException;
14
use ocramius\util\exception\NullPointerException;
15
use Throwable;
16
17
use function sprintf;
18
use function strval;
19
20
/**
21
 * A container object which may or may not contain a non-null value.
22
 * If a value is present, {@code isPresent()} will return {@code true} and
23
 * {@code get()} will return the value.
24
 *
25
 * <p>Additional methods that depend on the presence or absence of a contained
26
 * value are provided, such as {@link #orElse(java.lang.Object) orElse()}
27
 * (return a default value if value not present) and
28
 * {@link #ifPresent(java.util.function.Consumer) ifPresent()} (execute a block
29
 * of code if the value is present).
30
 *
31
 * <p>This is a value-based class; use of identity-sensitive operations
32
 * (including reference equality ({@code ==}), identity hash code, or
33
 * synchronization) on instances of {@code Optional} may have unpredictable
34
 * results and should be avoided.
35
 *
36
 * @template T The type of the value residing in the Optional wrapper.
37
 */
38
final class Optional
39
{
40
    /**
41
     * Common instance for {@code empty()}.
42
     */
43
    private static ?self $EMPTY = null;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected '?', expecting T_FUNCTION or T_CONST
Loading history...
44
45
    /**
46
     * If non-null, the value; if null, indicates no value is present
47
     *
48
     * @psalm-var T
49
     * @var mixed
50
     */
51
    private $value;
52
53
    /**
54
     * Constructs an empty instance.
55
     *
56
     * @implNote Generally only one empty instance, {@link Optional#EMPTY},
57
     * should exist per VM.
58
     * @psalm-return self<T>
59
     */
60 101
    private function __construct()
61
    {
62 101
    }
63
64
    /**
65
     * Returns an empty {@code Optional} instance.  No value is present for this
66
     * Optional.
67
     *
68
     * @return self an empty {@code Optional}
69
     *
70
     * @apiNote Though it may be tempting to do so, avoid testing if an object
71
     * is empty by comparing with {@code ==} against instances returned by
72
     * {@code Option.empty()}. There is no guarantee that it is a singleton.
73
     * Instead, use {@link #isPresent()}.
74
     */
75 68
    public static function newEmpty(): self
76
    {
77 68
        return self::$EMPTY ?: self::$EMPTY = new self();
78
    }
79
80
    /**
81
     * Returns an {@code Optional} with the specified present non-null value.
82
     *
83
     * @param mixed $value the value to be present, which must be non-null
84
     *
85
     * @return self an {@code Optional} with the value present
86
     *
87
     * @throws NullPointerException If value is null.
88
     *
89
     * @psalm-return self<T>
90
     * @psalm-template T
91
     * @psalm-param T $value
92
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration
93
     */
94 101
    public static function of($value): self
95
    {
96 101
        if ($value === null) {
97 1
            throw new NullPointerException();
98
        }
99
100 100
        $self = new self();
101
102 100
        $self->value = $value;
103
104 100
        return $self;
105
    }
106
107
    /**
108
     * Returns an {@code Optional} describing the specified value, if non-null,
109
     * otherwise returns an empty {@code Optional}.
110
     *
111
     * @param mixed|null $value the possibly-null value to describe
112
     *
113
     * @return self an {@code Optional} with a present value if the specified value
114
     * is non-null, otherwise an empty {@code Optional}
115
     *
116
     * @psalm-template T
117
     * @psalm-param T|null $value
118
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration
119
     */
120 26
    public static function ofNullable($value): self
121
    {
122 26
        return $value === null ? self::newEmpty() : self::of($value);
123
    }
124
125
    /**
126
     * If a value is present in this {@code Optional}, returns the value,
127
     * otherwise throws {@code NoSuchElementException}.
128
     *
129
     * @see Optional#isPresent()
130
     *
131
     * @return mixed the non-null value held by this {@code Optional}
132
     *
133
     * @throws NoSuchElementException If there is no value present.
134
     *
135
     * @psalm-return T
136
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration
137
     */
138 25
    public function get()
139
    {
140 25
        if ($this->value === null) {
141 1
            throw new NoSuchElementException('No value present');
142
        }
143
144 24
        return $this->value;
145
    }
146
147
    /**
148
     * Return {@code true} if there is a value present, otherwise {@code false}.
149
     *
150
     * @return bool {@code true} if there is a value present, otherwise {@code false}
151
     */
152 17
    public function isPresent(): bool
153
    {
154 17
        return $this->value !== null;
155
    }
156
157
    /**
158
     * If a value is present, invoke the specified consumer with the value,
159
     * otherwise do nothing.
160
     *
161
     * @param callable $consumer block to be executed if a value is present
162
     *
163
     * @psalm-param callable(T):void $consumer
164
     */
165 9
    public function ifPresent(callable $consumer): void
166
    {
167 9
        if ($this->value === null) {
168 1
            return;
169
        }
170
171 8
        $consumer($this->value);
172 8
    }
173
174
    /**
175
     * If a value is present, and the value matches the given predicate,
176
     * return an {@code Optional} describing the value, otherwise return an
177
     * empty {@code Optional}.
178
     *
179
     * @param callable $predicate a predicate to apply to the value, if present
180
     *
181
     * @return self an {@code Optional} describing the value of this {@code Optional}
182
     * if a value is present and the value matches the given predicate,
183
     * otherwise an empty {@code Optional}
184
     *
185
     * @throws NullPointerException If the predicate is null.
186
     *
187
     * @psalm-param callable(T):self $predicate
188
     */
189 17
    public function filter(callable $predicate): self
190
    {
191 17
        if ($this->value === null) {
192 1
            return $this;
193
        }
194
195 16
        return $predicate($this->value) ? $this : self::newEmpty();
196
    }
197
198
    /**
199
     * If a value is present, apply the provided mapping function to it,
200
     * and if the result is non-null, return an {@code Optional} describing the
201
     * result.  Otherwise return an empty {@code Optional}.
202
     *
203
     * @param callable $mapper a mapping function to apply to the value, if present
204
     *
205
     * @return self an {@code Optional} describing the result of applying a mapping
206
     * function to the value of this {@code Optional}, if a value is present,
207
     * otherwise an empty {@code Optional}
208
     *
209
     * @throws NullPointerException If the mapping function is null.
210
     *
211
     * @psalm-param callable(T):U $mapper
212
     * @psalm-return self<U>
213
     * @apiNote This method supports post-processing on optional values, without
214
     * the need to explicitly check for a return status.  For example, the
215
     * following code traverses a stream of file names, selects one that has
216
     * not yet been processed, and then opens that file, returning an
217
     * {@code Optional<FileInputStream>}:
218
     *
219
     * <pre>{@code
220
     *     Optional<FileInputStream> fis =
221
     *         names.stream().filter(name -> !isProcessedYet(name))
222
     *                       .findFirst()
223
     *                       .map(name -> new FileInputStream(name));
224
     * }</pre>
225
     *
226
     * Here, {@code findFirst} returns an {@code Optional<String>}, and then
227
     * {@code map} returns an {@code Optional<FileInputStream>} for the desired
228
     * file if one exists.
229
     * @template U
230
     */
231 10
    public function map(callable $mapper): self
232
    {
233 10
        if ($this->value === null) {
234 1
            return self::newEmpty();
235
        }
236
237 9
        return self::ofNullable($mapper($this->value));
238
    }
239
240
    /**
241
     * If a value is present, apply the provided {@code Optional}-bearing
242
     * mapping function to it, return that result, otherwise return an empty
243
     * {@code Optional}.  This method is similar to {@link #map(Function)},
244
     * but the provided mapper is one whose result is already an {@code Optional},
245
     * and if invoked, {@code flatMap} does not wrap it with an additional
246
     * {@code Optional}.
247
     *
248
     * @param callable $mapper a mapping function to apply to the value, if present
249
     *           the mapping function
250
     *
251
     * @return self the result of applying an {@code Optional}-bearing mapping
252
     * function to the value of this {@code Optional}, if a value is present,
253
     * otherwise an empty {@code Optional}
254
     *
255
     * @throws NullPointerException If the mapping function is null or returns
256
     * a null result.
257
     *
258
     * @template U
259
     * @psalm-param callable(T):self<U> $mapper
260
     * @psalm-return self<U>
261
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration
262
     */
263 10
    public function flatMap(callable $mapper): self
264
    {
265 10
        if ($this->value === null) {
266 1
            return self::newEmpty();
267
        }
268
269 9
        $result = $mapper($this->value);
270
271 9
        if ($result === null) {
272 1
            throw new NullPointerException();
273
        }
274
275 8
        return $result;
276
    }
277
278
    /**
279
     * Return the value if present, otherwise return {@code other}.
280
     *
281
     * @param mixed $other the value to be returned if there is no value present, may
282
     * be null
283
     *
284
     * @return mixed the value, if present, otherwise {@code other}
285
     *
286
     * @psalm-param T $other
287
     * @psalm-return T
288
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration
289
     */
290 16
    public function orElse($other)
291
    {
292 16
        return $this->value ?? $other;
293
    }
294
295
    /**
296
     * Return the value if present, otherwise invoke {@code other} and return
297
     * the result of that invocation.
298
     *
299
     * @param callable $other a {@code Supplier} whose result is returned if no value
300
     * is present
301
     *
302
     * @return mixed the value if present otherwise the result of {@code other.get()}
303
     *
304
     * @throws NullPointerException If value is not present and {@code other} is
305
     * null.
306
     *
307
     * @psalm-param callable():T $other
308
     * @psalm-return T
309
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration
310
     */
311 16
    public function orElseGet(callable $other)
312
    {
313 16
        return $this->value ?? $other();
314
    }
315
316
    /**
317
     * Return the contained value, if present, otherwise throw an exception
318
     * to be created by the provided supplier.
319
     *
320
     * @param callable $exceptionSupplier The supplier which will return the exception to
321
     * be thrown
322
     *
323
     * @return mixed the present value
324
     *
325
     * @throws Exception If there is no value present.
326
     * @throws NullPointerException If no value is present and
327
     * {@code exceptionSupplier} is null.
328
     *
329
     * @psalm-param callable(): Throwable $exceptionSupplier
330
     * @psalm-return T
331
     * @apiNote A method reference to the exception constructor with an empty
332
     * argument list can be used as the supplier. For example,
333
     * {@code IllegalStateException::new}
334
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.TypeHintDeclaration
335
     */
336 9
    public function orElseThrow(callable $exceptionSupplier)
337
    {
338 9
        if ($this->value === null) {
339 1
            throw $exceptionSupplier();
340
        }
341
342 8
        return $this->value;
343
    }
344
345
    /**
346
     * Indicates whether some other object is "equal to" this Optional. The
347
     * other object is considered equal if:
348
     * <ul>
349
     * <li>it is also an {@code Optional} and;
350
     * <li>both instances have no value present or;
351
     * <li>the present values are "equal to" each other via {@code equals()}.
352
     * </ul>
353
     *
354
     * @param mixed $object an object to be tested for equality
355
     *
356
     * @return bool {code true} if the other object is "equal to" this object
357
     * otherwise {@code false}
358
     */
359 17
    public function equals($object): bool
360
    {
361 17
        return $object === $this || ($object instanceof self && $object->value === $this->value);
362
    }
363
364
    /**
365
     * Returns a non-empty string representation of this Optional suitable for
366
     * debugging. The exact presentation format is unspecified and may vary
367
     * between implementations and versions.
368
     *
369
     * @return string the string representation of this instance
370
     *
371
     * @implSpec If a value is present the result must include its string
372
     * representation in the result. Empty and present Optionals must be
373
     * unambiguously differentiable.
374
     */
375 1
    public function __toString(): string
376
    {
377 1
        return $this->value === null ? 'Optional.empty' : sprintf('Optional[%s]', strval($this->value));
378
    }
379
}
380