Completed
Branch feature/first-implementation (aa6e55)
by Andrea Marco
10:09
created

Dto::__get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 3
cts 3
cp 1
crap 1
rs 10
1
<?php
2
3
namespace Cerbero\Dto;
4
5
use ArrayAccess;
6
use ArrayIterator;
7
use Cerbero\Dto\Exceptions\ImmutableDtoException;
8
use Cerbero\Dto\Exceptions\UnknownDtoPropertyException;
9
use Cerbero\Dto\Exceptions\UnsetDtoPropertyException;
10
use IteratorAggregate;
11
use JsonSerializable;
12
use Serializable;
13
use Traversable;
14
15
/**
16
 * The data transfer object.
17
 *
18
 */
19
abstract class Dto implements IteratorAggregate, ArrayAccess, Serializable, JsonSerializable
20
{
21
    /**
22
     * The default flags.
23
     *
24
     * @var int
25
     */
26
    protected static $defaultFlags = NONE;
27
28
    /**
29
     * The array converter.
30
     *
31
     * @var ArrayConverter
32
     */
33
    protected static $arrayConverter;
34
35
    /**
36
     * The actual flags.
37
     *
38
     * @var int
39
     */
40
    protected $flags;
41
42
    /**
43
     * The properties map.
44
     *
45
     * @var array
46
     */
47
    protected $propertiesMap;
48
49
    /**
50
     * Instantiate the class.
51
     *
52
     * @param array $data
53
     * @param int $flags
54
     */
55 156
    public function __construct(array $data = [], int $flags = NONE)
56
    {
57 156
        $this->flags = $this->mergeFlags(static::getDefaultFlags(), $flags);
58 156
        $this->propertiesMap = $this->mapData($data);
59 156
    }
60
61
    /**
62
     * Retrieve the merged flags
63
     *
64
     * @param int $initialFlags
65
     * @param int $flagsToMerge
66
     * @return int
67
     * @throws Exceptions\IncompatibleDtoFlagsException
68
     */
69 156
    protected function mergeFlags(int $initialFlags, int $flagsToMerge): int
70
    {
71 156
        return (new DtoFlagsHandler)->merge($initialFlags, $flagsToMerge);
72
    }
73
74
    /**
75
     * Retrieve the DTO mapped properties for the given data
76
     *
77
     * @param array $data
78
     * @return array
79
     * @throws Exceptions\DtoNotFoundException
80
     */
81 156
    protected function mapData(array $data): array
82
    {
83 156
        return DtoPropertiesMapper::for(static::class)->map($data, $this->getFlags());
84
    }
85
86
    /**
87
     * Retrieve an instance of DTO
88
     *
89
     * @param array $data
90
     * @param int $flags
91
     * @return self
92
     */
93 138
    public static function make(array $data = [], int $flags = NONE): self
94
    {
95 138
        return new static($data, $flags);
96
    }
97
98
    /**
99
     * Retrieve the default flags
100
     *
101
     * @return int
102
     */
103 159
    public static function getDefaultFlags(): int
104
    {
105 159
        return static::$defaultFlags;
106
    }
107
108
    /**
109
     * Retrieve the array converter
110
     *
111
     * @return ArrayConverter
112
     */
113 33
    public static function getArrayConverter(): ArrayConverter
114
    {
115 33
        return static::$arrayConverter ?: ArrayConverter::instance();
116
    }
117
118
    /**
119
     * Set the given array converter
120
     *
121
     * @param ArrayConverter
122
     * @return void
123
     */
124 3
    public static function setArrayConverter(ArrayConverter $converter): void
125
    {
126 3
        static::$arrayConverter = $converter;
127 3
    }
128
129
    /**
130
     * Retrieve the DTO properties map
131
     *
132
     * @return array
133
     */
134 36
    public function getPropertiesMap(): array
135
    {
136 36
        return $this->propertiesMap;
137
    }
138
139
    /**
140
     * Retrieve the DTO flags
141
     *
142
     * @return int
143
     */
144 156
    public function getFlags(): int
145
    {
146 156
        return $this->flags;
147
    }
148
149
    /**
150
     * Retrieve the DTO property names
151
     *
152
     * @return array
153
     */
154 6
    public function getPropertyNames(): array
155
    {
156 6
        return array_keys($this->getPropertiesMap());
157
    }
158
159
    /**
160
     * Retrieve the DTO properties
161
     *
162
     * @return DtoProperty[]
163
     */
164 6
    public function getProperties(): array
165
    {
166 6
        return array_values($this->getPropertiesMap());
167
    }
168
169
    /**
170
     * Determine whether the given property is set (even if its value is NULL)
171
     *
172
     * @param string $property
173
     * @return bool
174
     */
175 27
    public function hasProperty(string $property): bool
176
    {
177
        try {
178 27
            return !!$this->getProperty($property);
179 18
        } catch (UnknownDtoPropertyException $e) {
180 18
            return false;
181
        }
182
    }
183
184
    /**
185
     * Retrieve the given DTO property (support dot notation)
186
     *
187
     * @param string $property
188
     * @return DtoProperty
189
     * @throws UnknownDtoPropertyException
190
     */
191 96
    public function getProperty(string $property): DtoProperty
192
    {
193 96
        if (isset($this->propertiesMap[$property])) {
194 63
            return $this->propertiesMap[$property];
195
        }
196
197 51
        if (strpos($property, '.') === false) {
198 45
            throw new UnknownDtoPropertyException(static::class, $property);
199
        }
200
201 12
        [$property, $nestedProperty] = explode('.', $property, 2);
202 12
        $presumedDto = $this->get($property);
203
204 9
        if ($presumedDto instanceof self) {
205 6
            return $presumedDto->getProperty($nestedProperty);
206
        }
207
208 3
        throw new UnknownDtoPropertyException(static::class, $nestedProperty);
209
    }
210
211
    /**
212
     * Determine whether the given property has a value (return FALSE if the value is NULL)
213
     *
214
     * @param string $property
215
     * @return bool
216
     */
217 30
    public function has(string $property): bool
218
    {
219
        try {
220 30
            return $this->get($property) !== null;
221 9
        } catch (UnknownDtoPropertyException $e) {
222 9
            return false;
223
        }
224
    }
225
226
    /**
227
     * Retrieve the given property value
228
     *
229
     * @param string $property
230
     * @return mixed
231
     * @throws UnknownDtoPropertyException
232
     */
233 78
    public function get(string $property)
234
    {
235 78
        return $this->getProperty($property)->value();
236
    }
237
238
    /**
239
     * Set the given property to the provided value
240
     *
241
     * @param string $property
242
     * @param mixed $value
243
     * @return self
244
     * @throws UnknownDtoPropertyException
245
     */
246 18
    public function set(string $property, $value): self
247
    {
248 18
        $flags = $this->getFlags();
249 18
        $dto = ($flags & MUTABLE) ? $this : $this->clone();
250
251
        try {
252 18
            $dto->getProperty($property)->setValue($value, $flags);
253 6
        } catch (UnknownDtoPropertyException $e) {
254 6
            if (!($flags & IGNORE_UNKNOWN_PROPERTIES)) {
255 3
                throw $e;
256
            }
257
        }
258
259 15
        return $dto;
260
    }
261
262
    /**
263
     * Retrieve a clone of the DTO
264
     *
265
     * @return self
266
     */
267 12
    public function clone(): self
268
    {
269 12
        return clone $this;
270
    }
271
272
    /**
273
     * Merge the given data in the DTO
274
     *
275
     * @param iterable $data
276
     * @param int $flags
277
     * @return self
278
     */
279 9
    public function merge(iterable $data, int $flags = NONE): self
280
    {
281 9
        $replacements = static::getArrayConverter()->convert($data);
282 9
        $mergedData = array_replace_recursive($this->toArray(), $replacements);
283 9
        $mergedFlags = $this->mergeFlags($this->getFlags(), $flags);
284
285 9
        if (!($this->getFlags() & MUTABLE)) {
286 6
            return new static($mergedData, $mergedFlags);
287
        }
288
289 3
        $this->flags = $mergedFlags;
290 3
        $this->propertiesMap = $this->mapData($mergedData);
291
292 3
        return $this;
293
    }
294
295
    /**
296
     * Retrieve the DTO as an array
297
     *
298
     * @return array
299
     */
300 30
    public function toArray(): array
301
    {
302 30
        $data = [];
303
304 30
        foreach ($this->getPropertiesMap() as $name => $property) {
305 30
            $data[$name] = static::getArrayConverter()->convert($property->value());
306
        }
307
308 30
        return $data;
309
    }
310
311
    /**
312
     * Retrieve the DTO as a JSON string
313
     *
314
     * @param int $options
315
     * @return string|false
316
     */
317 6
    public function toJson($options = 0)
318
    {
319 6
        return json_encode($this->toArray(), $options);
320
    }
321
322
    /**
323
     * Retrieve the DTO as an iterator
324
     *
325
     * @return Traversable
326
     */
327 12
    public function getIterator(): Traversable
328
    {
329 12
        return new ArrayIterator($this->toArray(), ArrayIterator::ARRAY_AS_PROPS);
330
    }
331
332
    /**
333
     * Determine whether a given property has a value
334
     *
335
     * @param mixed $property
336
     * @return bool
337
     */
338 9
    public function offsetExists($property): bool
339
    {
340 9
        return $this->has($property);
341
    }
342
343
    /**
344
     * Retrieve the given property value
345
     *
346
     * @param mixed $property
347
     * @return mixed
348
     * @throws UnknownDtoPropertyException
349
     */
350 6
    public function &offsetGet($property)
351
    {
352 6
        $value = $this->get($property);
353
354 3
        return $value;
355
    }
356
357
    /**
358
     * Set the given property to the provided value
359
     *
360
     * @param mixed $property
361
     * @param mixed $value
362
     * @return void
363
     * @throws ImmutableDtoException
364
     * @throws UnknownDtoPropertyException
365
     */
366 12
    public function offsetSet($property, $value): void
367
    {
368 12
        if (!($this->getFlags() & MUTABLE)) {
369 6
            throw new ImmutableDtoException(static::class);
370
        }
371
372 6
        $this->set($property, $value);
373 6
    }
374
375
    /**
376
     * Set the given property to the provided value
377
     *
378
     * @param mixed $property
379
     * @return void
380
     * @throws ImmutableDtoException
381
     * @throws UnsetDtoPropertyException
382
     * @throws UnknownDtoPropertyException
383
     */
384 12
    public function offsetUnset($property): void
385
    {
386 12
        $flags = $this->getFlags();
387
388 12
        if (!($flags & MUTABLE)) {
389 3
            throw new ImmutableDtoException(static::class);
390 9
        } elseif (!($flags & PARTIAL)) {
391 3
            throw new UnsetDtoPropertyException(static::class, $property);
392 6
        } elseif (!$this->hasProperty($property)) {
393 3
            throw new UnknownDtoPropertyException(static::class, $property);
394
        }
395
396 3
        unset($this->propertiesMap[$property]);
397 3
    }
398
399
    /**
400
     * Retrieve the serialized DTO
401
     *
402
     * @return string
403
     */
404 3
    public function serialize(): string
405
    {
406 3
        return serialize([
407 3
            $this->toArray(),
408 3
            $this->getFlags(),
409
        ]);
410
    }
411
412
    /**
413
     * Retrieve the unserialized DTO
414
     *
415
     * @param mixed $serialized
416
     * @return string
417
     */
418 3
    public function unserialize($serialized): void
419
    {
420 3
        [$data, $flags] = unserialize($serialized);
421
422 3
        $this->__construct($data, $flags);
423 3
    }
424
425
    /**
426
     * Retrieve the JSON serialized DTO
427
     *
428
     * @return array
429
     */
430 3
    public function jsonSerialize(): array
431
    {
432 3
        return $this->toArray();
433
    }
434
435
    /**
436
     * Determine whether a given property has a value
437
     *
438
     * @param string $property
439
     * @return bool
440
     */
441 9
    public function __isset(string $property): bool
442
    {
443 9
        return $this->has($property);
444
    }
445
446
    /**
447
     * Retrieve the given property value
448
     *
449
     * @param string $property
450
     * @return mixed
451
     * @throws UnknownDtoPropertyException
452
     */
453 27
    public function &__get(string $property)
454
    {
455 27
        $value = $this->get($property);
456
457 24
        return $value;
458
    }
459
460
    /**
461
     * Set the given property to the provided value
462
     *
463
     * @param string $property
464
     * @param mixed $value
465
     * @return void
466
     * @throws ImmutableDtoException
467
     * @throws UnknownDtoPropertyException
468
     */
469 6
    public function __set(string $property, $value): void
470
    {
471 6
        $this->offsetSet($property, $value);
472 3
    }
473
474
    /**
475
     * Retrieve the string representation of the DTO
476
     *
477
     * @return string
478
     */
479 3
    public function __toString(): string
480
    {
481 3
        return $this->toJson();
482
    }
483
484
    /**
485
     * Determine how to clone the DTO
486
     *
487
     * @return void
488
     */
489 12
    public function __clone()
490
    {
491 12
        foreach ($this->propertiesMap as &$property) {
492 12
            $property = clone $property;
493
        }
494 12
    }
495
}
496