MapperHydrator::getOwnerObject()   A
last analyzed

Complexity

Conditions 6
Paths 8

Size

Total Lines 37
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 37
ccs 19
cts 19
cp 1
rs 9.0111
c 0
b 0
f 0
cc 6
nc 8
nop 2
crap 6
1
<?php
2
3
namespace Bdf\Prime\Entity\Hydrator;
4
5
use Bdf\Prime\Entity\Hydrator\Exception\FieldNotDeclaredException;
6
use Bdf\Prime\Entity\Hydrator\Exception\InvalidTypeException;
7
use Bdf\Prime\Entity\Hydrator\Exception\UninitializedPropertyException;
8
use Bdf\Prime\Entity\Instantiator\InstantiatorInterface;
9
use Bdf\Prime\Mapper\Metadata;
10
use Bdf\Prime\Platform\PlatformTypesInterface;
11
use Error;
12
use ReflectionException;
13
use ReflectionProperty;
14
use stdClass;
15
use TypeError;
16
17
/**
18
 * Base implementation for @see MapperHydratorInterface
19
 *
20
 * Prefer use generated hydrators on production
21
 */
22
class MapperHydrator implements MapperHydratorInterface
23
{
24
    /**
25
     * @var InstantiatorInterface
26
     */
27
    protected $instantiator;
28
29
    /**
30
     * @var Metadata
31
     */
32
    protected $metadata;
33
34
    /**
35
     * Property accessors, indexed by attribute name
36
     *
37
     * @var ReflectionProperty[][]
38
     */
39
    private $reflectionProperties = [];
40
41
    /**
42
     * {@inheritdoc}
43
     */
44 515
    public function setPrimeInstantiator(InstantiatorInterface $instantiator): void
45
    {
46 515
        $this->instantiator = $instantiator;
47
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52 515
    public function setPrimeMetadata(Metadata $metadata): void
53
    {
54 515
        $this->metadata = $metadata;
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 662
    public function flatExtract($object, array $attributes = null): array
61
    {
62 662
        $values = [];
63 662
        $cache  = [];
64
65 662
        $attributes = $attributes === null
66 656
            ? $this->metadata->attributes
67 662
            : array_intersect_key($this->metadata->attributes, $attributes);
68
69 662
        foreach ($attributes as $attribute => $metadata) {
70 662
            if (isset($metadata['embedded'])) {
71 522
                $path = $metadata['embedded'];
72
73 522
                if (empty($cache[$path])) {
74 522
                    $cache[$path] = $this->getOwnerObject($object, $metadata);
75
                }
76
77 522
                $value = $cache[$path] ? $this->readFromAttribute($cache[$path], $metadata) : null;
78
            } else {
79 662
                $value = $this->readFromAttribute($object, $metadata);
80
            }
81
82 660
            $values[$attribute] = $value;
83
        }
84
85 660
        return $values;
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91 398
    public function flatHydrate($object, array $data, PlatformTypesInterface $types): void
92
    {
93 398
        $metadata  = $this->metadata->fields;
94 398
        $embeddeds = $this->metadata->embeddeds;
95 398
        $cacheEmbedded = [
96 398
            'root' => $object
97 398
        ];
98
99 398
        foreach ($data as $field => $value) {
100 398
            if (!isset($metadata[$field])) {
101 1
                continue;
102
            }
103
104 398
            $value = $types->get($metadata[$field]['type'])->fromDatabase($value, $metadata[$field]['phpOptions']);
105
106 398
            if (isset($metadata[$field]['embedded'])) {
107 266
                $path = $metadata[$field]['embedded'];
108
109
                //creation du cache d'objet. Le but est de parcourir les paths de l'embedded
110
                //de creer les objets et de les associés entre eux
111
                //ex:
112
                //  'root.wrapper.offer.id'  id est embedded dans offer qui est embedded dans wrapper, etc...
113
                //   l'attribute id peut etre parsé sans que wrapper ne soit déjà construit (parsqu'il n'a pas d'attribut,
114
                //   ou parce qu'il est définit avant dans le select)
115 266
                if (empty($cacheEmbedded[$path])) {
116 266
                    for ($i = 0, $l = count($embeddeds[$path]['paths']); $i < $l; $i++) {
117 266
                        $pathCursor = $embeddeds[$path]['paths'][$i];
118
119 266
                        if (empty($cacheEmbedded[$pathCursor])) {
120 266
                            $parentMeta = $embeddeds[$pathCursor];
121
122 266
                            if (empty($parentMeta['polymorph'])) {
123 266
                                $cacheEmbedded[$pathCursor] = $this->instantiator->instantiate($parentMeta['class'], $parentMeta['hint']);
124
                            } else {
125 1
                                $cacheEmbedded[$pathCursor] = $this->instantiator->instantiate(
126 1
                                    $className = $parentMeta['class_map'][$data[$parentMeta['discriminator_field']]],
127 1
                                    $parentMeta['hints'][$className]
128 1
                                );
129
                            }
130
131 266
                            $this->writeToEmbedded(
132 266
                                $cacheEmbedded[$parentMeta['parentPath']],
133 266
                                $parentMeta,
0 ignored issues
show
Bug introduced by
$parentMeta of type Bdf\Prime\Mapper\EmbeddedMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...ator::writeToEmbedded(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

133
                                /** @scrutinizer ignore-type */ $parentMeta,
Loading history...
134 266
                                $cacheEmbedded[$parentMeta['path']],
135 266
                                true
136 266
                            );
137
                        }
138
                    }
139
                }
140
141 266
                $this->writeToAttribute($cacheEmbedded[$path], $metadata[$field], $value, true);
0 ignored issues
show
Bug introduced by
$metadata[$field] of type Bdf\Prime\Mapper\FieldMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...tor::writeToAttribute(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

141
                $this->writeToAttribute($cacheEmbedded[$path], /** @scrutinizer ignore-type */ $metadata[$field], $value, true);
Loading history...
142
            } else {
143 395
                $this->writeToAttribute($object, $metadata[$field], $value, true);
144
            }
145
        }
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151 362
    public function extractOne($object, string $attribute)
152
    {
153 362
        if (!isset($this->metadata->attributes[$attribute])) {
154 58
            if (!isset($this->metadata->embeddeds[$attribute])) {
155 2
                throw new FieldNotDeclaredException($this->metadata->entityClass, $attribute);
156
            }
157
158 56
            return $this->readFromEmbedded($object, $this->metadata->embeddeds[$attribute]);
0 ignored issues
show
Bug introduced by
$this->metadata->embeddeds[$attribute] of type Bdf\Prime\Mapper\EmbeddedMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...tor::readFromEmbedded(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

158
            return $this->readFromEmbedded($object, /** @scrutinizer ignore-type */ $this->metadata->embeddeds[$attribute]);
Loading history...
159
        } else {
160 355
            $ownerObject = $this->getOwnerObject($object, $this->metadata->attributes[$attribute]);
0 ignored issues
show
Bug introduced by
$this->metadata->attributes[$attribute] of type Bdf\Prime\Mapper\FieldMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...rator::getOwnerObject(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

160
            $ownerObject = $this->getOwnerObject($object, /** @scrutinizer ignore-type */ $this->metadata->attributes[$attribute]);
Loading history...
161
162
            // Polymorphic embedded not instantiated
163 355
            if ($ownerObject === null) {
164 1
                return null;
165
            }
166
167 355
            return $this->readFromAttribute($ownerObject, $this->metadata->attributes[$attribute]);
0 ignored issues
show
Bug introduced by
$this->metadata->attributes[$attribute] of type Bdf\Prime\Mapper\FieldMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...or::readFromAttribute(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

167
            return $this->readFromAttribute($ownerObject, /** @scrutinizer ignore-type */ $this->metadata->attributes[$attribute]);
Loading history...
168
        }
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174 304
    public function hydrateOne($object, string $attribute, $value): void
175
    {
176 304
        if (!isset($this->metadata->attributes[$attribute])) {
177 176
            if (!isset($this->metadata->embeddeds[$attribute])) {
178 2
                throw new FieldNotDeclaredException($this->metadata->entityClass, $attribute);
179
            }
180
181 174
            $this->writeToEmbedded($object, $this->metadata->embeddeds[$attribute], $value, false);
0 ignored issues
show
Bug introduced by
$this->metadata->embeddeds[$attribute] of type Bdf\Prime\Mapper\EmbeddedMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...ator::writeToEmbedded(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

181
            $this->writeToEmbedded($object, /** @scrutinizer ignore-type */ $this->metadata->embeddeds[$attribute], $value, false);
Loading history...
182
        } else {
183 140
            $ownerObject = $this->getOwnerObject($object, $this->metadata->attributes[$attribute]);
0 ignored issues
show
Bug introduced by
$this->metadata->attributes[$attribute] of type Bdf\Prime\Mapper\FieldMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...rator::getOwnerObject(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

183
            $ownerObject = $this->getOwnerObject($object, /** @scrutinizer ignore-type */ $this->metadata->attributes[$attribute]);
Loading history...
184
185
            // Polymorphic embedded not instantiated
186 140
            if ($ownerObject === null) {
187 1
                throw new \InvalidArgumentException('Cannot write to attribute '.$attribute.' : the embedded entity cannot be resolved');
188
            }
189
190 139
            $this->writeToAttribute($ownerObject, $this->metadata->attributes[$attribute], $value, false);
0 ignored issues
show
Bug introduced by
$this->metadata->attributes[$attribute] of type Bdf\Prime\Mapper\FieldMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...tor::writeToAttribute(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

190
            $this->writeToAttribute($ownerObject, /** @scrutinizer ignore-type */ $this->metadata->attributes[$attribute], $value, false);
Loading history...
191
        }
192
    }
193
194
    /**
195
     * Get owner object attribute
196
     *
197
     * @param object $entity
198
     * @param array $metadata Metadata d'un attribut a retrouver
199
     *
200
     * @return object|null The object, or null if cannot be instantiated (polymorph)
201
     */
202 648
    protected function getOwnerObject($entity, array $metadata)
203
    {
204 648
        if (!isset($metadata['embedded'])) {
205 412
            return $entity;
206
        }
207
208 540
        $embeddeds = $this->metadata->embeddeds;
209 540
        $current = $entity;
210 540
        $embeddedMeta = $embeddeds[$metadata['embedded']];
211 540
        $embedded = null;
212
213
        //parcourt des paths pour descendre juqu'à l'objet propriétaire des meta données
214
        //si l'objet n'existe pas, on le créé avant de l'associer à son object parent
215 540
        for ($i = 0, $l = count($embeddedMeta['paths']); $i < $l; $i++) {
216 540
            $parentMeta = $embeddeds[$embeddedMeta['paths'][$i]];
217
218
            try {
219 540
                $embedded = $this->readFromEmbedded($current, $parentMeta);
0 ignored issues
show
Bug introduced by
$parentMeta of type Bdf\Prime\Mapper\EmbeddedMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...tor::readFromEmbedded(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

219
                $embedded = $this->readFromEmbedded($current, /** @scrutinizer ignore-type */ $parentMeta);
Loading history...
220 163
            } catch (UninitializedPropertyException $e) { // The property is not initialized
221 163
                $embedded = null;
222
            }
223
224 540
            if ($embedded === null) {
225 375
                if (!isset($parentMeta['class'])) {
226 3
                    return null;
227
                }
228
229 373
                $embedded = $this->instantiator->instantiate($parentMeta['class'], $parentMeta['hint']);
230
231
                // This writes should never fail : $embedded is not null
232 373
                $this->writeToEmbedded($current, $parentMeta, $embedded, false);
0 ignored issues
show
Bug introduced by
$parentMeta of type Bdf\Prime\Mapper\EmbeddedMetadata is incompatible with the type array expected by parameter $metadata of Bdf\Prime\Entity\Hydrato...ator::writeToEmbedded(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

232
                $this->writeToEmbedded($current, /** @scrutinizer ignore-type */ $parentMeta, $embedded, false);
Loading history...
233
            }
234
235 539
            $current = $embedded;
236
        }
237
238 539
        return $embedded;
239
    }
240
241
    /**
242
     * Read a value from an entity, with attribute metadata
243
     *
244
     * @param object $entity
245
     * @param array $metadata
246
     *
247
     * @return mixed
248
     *
249
     * @throws ReflectionException When property do not exist on the object
250
     * @throws UninitializedPropertyException When the property is not initialized
251
     */
252 687
    protected function readFromAttribute($entity, array $metadata)
253
    {
254 687
        $attribute = $metadata['attribute'];
255 687
        $class = get_class($entity);
256
257 687
        if (isset($this->reflectionProperties[$class][$attribute])) {
258
            try {
259 656
                return $this->reflectionProperties[$class][$attribute]->getValue($entity);
260 2
            } catch (Error $e) {
261 2
                throw new UninitializedPropertyException($class, $this->reflectionProperties[$class][$attribute]->name, $e);
262
            }
263
        }
264
265 335
        if (!isset($metadata['embedded'])) {
266 329
            $property = $attribute;
267
        } else {
268 243
            $property = substr($attribute, strlen($metadata['embedded']) + 1);
269
        }
270
271 335
        return $this->readFromProperty($class, $property, $attribute, $entity);
272
    }
273
274
    /**
275
     * Read a value from an entity, with embedded metadata
276
     *
277
     * @param object $entity
278
     * @param array $metadata
279
     *
280
     * @return mixed
281
     *
282
     * @throws ReflectionException When property do not exist on the object
283
     * @throws UninitializedPropertyException When the property is not initialized
284
     */
285 557
    protected function readFromEmbedded($entity, array $metadata)
286
    {
287 557
        $attribute = $metadata['path'];
288 557
        $class = get_class($entity);
289
290 557
        if (isset($this->reflectionProperties[$class][$attribute])) {
291
            try {
292 518
                return $this->reflectionProperties[$class][$attribute]->getValue($entity);
293 124
            } catch (Error $e) {
294 124
                throw new UninitializedPropertyException($class, $this->reflectionProperties[$class][$attribute]->name, $e);
295
            }
296
        }
297
298 255
        if ($metadata['parentPath'] === 'root') {
299 255
            $property = $attribute;
300
        } else {
301 170
            $property = substr($attribute, strlen($metadata['parentPath']) + 1);
302
        }
303
304 255
        return $this->readFromProperty($class, $property, $attribute, $entity);
305
    }
306
307
    /**
308
     * @param object $entity
309
     * @param array $metadata
310
     * @param mixed $value
311
     *
312
     * @return void
313
     */
314 464
    protected function writeToAttribute($entity, array $metadata, $value, bool $skipInvalid)
315
    {
316 464
        $attribute = $metadata['attribute'];
317 464
        $class = get_class($entity);
318
319 464
        if (isset($this->reflectionProperties[$class][$attribute])) {
320 416
            $this->writeToReflection($this->reflectionProperties[$class][$attribute], $entity, $value, $skipInvalid, $metadata);
321 416
            return;
322
        }
323
324 77
        if (!isset($metadata['embedded'])) {
325 67
            $property = $attribute;
326
        } else {
327 19
            $property = substr($attribute, strlen($metadata['embedded']) + 1);
328
        }
329
330 77
        if ($class === stdClass::class) {
331 5
            $entity->{$property} = $value;
332 5
            return;
333
        }
334
335 72
        $this->reflectionProperties[$class][$attribute] = $reflectionProperty = new ReflectionProperty($class, $property);
336 72
        $reflectionProperty->setAccessible(true);
337
338 72
        $this->writeToReflection($reflectionProperty, $entity, $value, $skipInvalid, $metadata);
339
    }
340
341
    /**
342
     * @param object $entity
343
     * @param array $metadata
344
     * @param mixed $value
345
     *
346
     * @return void
347
     */
348 500
    protected function writeToEmbedded($entity, array $metadata, $value, bool $skipInvalid)
349
    {
350 500
        $attribute = $metadata['path'];
351 500
        $class = get_class($entity);
352
353 500
        if (isset($this->reflectionProperties[$class][$attribute])) {
354 480
            $this->writeToReflection($this->reflectionProperties[$class][$attribute], $entity, $value, $skipInvalid, $metadata);
355 480
            return;
356
        }
357
358 89
        if ($metadata['parentPath'] === 'root') {
359 85
            $property = $attribute;
360
        } else {
361 5
            $property = substr($attribute, strlen($metadata['parentPath']) + 1);
362
        }
363
364 89
        if ($class === stdClass::class) {
365 3
            $entity->{$property} = $value;
366 3
            return;
367
        }
368
369 86
        $this->reflectionProperties[$class][$attribute] = $reflectionProperty = new ReflectionProperty($class, $property);
370 86
        $reflectionProperty->setAccessible(true);
371
372 86
        $this->writeToReflection($reflectionProperty, $entity, $value, $skipInvalid, $metadata);
373
    }
374
375
    /**
376
     * Simple property read
377
     *
378
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
379
     * @param string $property
380
     * @param string $attribute
381
     * @param object $entity
382
     * @return mixed|null
383
     *
384
     * @throws ReflectionException
385
     * @throws UninitializedPropertyException
386
     */
387 342
    private function readFromProperty(string $class, string $property, string $attribute, $entity)
388
    {
389 342
        if ($class === stdClass::class) {
390 9
            return $entity->$property ?? null;
391
        }
392
393 337
        $this->reflectionProperties[$class][$attribute] = $propertyReflection = new ReflectionProperty($class, $property);
394 337
        $propertyReflection->setAccessible(true);
395
396
        try {
397 337
            return $propertyReflection->getValue($entity);
398 168
        } catch (Error $e) {
399 168
            throw new UninitializedPropertyException($class, $property, $e);
400
        }
401
    }
402
403
    /**
404
     * Check if the value should not be hydrated, in case of null value on not nullable property
405
     *
406
     * @param ReflectionProperty $property
407
     * @param mixed $value
408
     *
409
     * @return bool
410
     */
411 394
    private function shouldSkipValue(ReflectionProperty $property, $value): bool
412
    {
413 394
        if (PHP_VERSION_ID < 70400 || $value !== null) {
414 394
            return false;
415
        }
416
417
        /** @psalm-suppress UndefinedMethod */
418 285
        return $property->hasType() && !$property->getType()->allowsNull();
419
    }
420
421
    /**
422
     * @param ReflectionProperty $reflectionProperty
423
     * @param object $entity
424
     * @param mixed $value
425
     * @param bool $skipInvalid
426
     * @param array $metadata
427
     *
428
     * @throws InvalidTypeException
429
     *
430
     * @return void
431
     */
432 581
    private function writeToReflection(ReflectionProperty $reflectionProperty, $entity, $value, bool $skipInvalid, array $metadata): void
433
    {
434 581
        if (!$skipInvalid || !$this->shouldSkipValue($reflectionProperty, $value)) {
435
            try {
436 581
                $reflectionProperty->setValue($entity, $value);
437 2
            } catch (TypeError $e) {
438 2
                throw new InvalidTypeException($e, $metadata['type'] ?? null);
439
            }
440
        }
441
    }
442
}
443