Completed
Pull Request — master (#43)
by Niklas
06:45
created

Optional::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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