Cancelled
Pull Request — 2.x (#432)
by Aleksei
18:59
created

ClosureHydrator::hydrate()   B

Complexity

Conditions 7
Paths 15

Size

Total Lines 36
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 21
nc 15
nop 4
dl 0
loc 36
ccs 18
cts 18
cp 1
crap 7
rs 8.6506
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\ORM\Mapper\Proxy\Hydrator;
6
7
use Closure;
8
use Cycle\ORM\EntityProxyInterface;
9
use Cycle\ORM\Exception\MapperException;
10
use Cycle\ORM\Reference\ReferenceInterface;
11
use Cycle\ORM\RelationMap;
12
13
/**
14
 * @internal
15
 */
16
class ClosureHydrator
17
{
18
    /**
19
     * @param array<string, PropertyMap> $propertyMaps Array of class properties
20 4804
     */
21
    public function hydrate(RelationMap $relMap, array $propertyMaps, object $object, array $data): object
22 4804
    {
23
        $isProxy = $object instanceof EntityProxyInterface;
24 4804
25 4804
        $properties = $propertyMaps[ClassPropertiesExtractor::KEY_FIELDS]->getProperties();
26
        $this->setEntityProperties($properties, $object, $data);
27 4804
28 1458
        if (!$isProxy) {
29 1458
            $properties = $propertyMaps[ClassPropertiesExtractor::KEY_RELATIONS]->getProperties();
30 928
            if ($properties !== []) {
31
                $this->setRelationProperties($properties, $relMap, $object, $data);
32
            }
33
        }
34 4804
35 4702
        foreach ($data as $property => $value) {
36
            try {
37
                if (isset($relMap->getRelations()[$property])) {
38 4804
                    unset($object->{$property});
39
                }
40
                // Use @ to try to ignore deprecations
41
                @$object->{$property} = $value;
42
            } catch (\Throwable $e) {
43
                if ($e::class === \TypeError::class) {
44 4804
                    throw new MapperException(
45
                        \sprintf(
46 4804
                            "Can't hydrate %s.%s because property and value types are incompatible.",
47 4804
                            \get_parent_class($object),
48 4790
                            $property,
49
                        ),
50
                        previous: $e
51 3340
                    );
52 3340
                }
53 3340
            }
54 3208
        }
55
56
        return $object;
57 244
    }
58 244
59
    /**
60
     * Map private entity properties
61
     */
62
    private function setEntityProperties(array $properties, object $object, array &$data): void
63
    {
64
        foreach ($properties as $class => $props) {
65
            if ($class === '') {
66
                continue;
67 928
            }
68
69 928
            Closure::bind(static function (object $object, array $props, array &$data): void {
70
                foreach ($props as $property) {
71 928
                    if (!array_key_exists($property, $data)) {
72 928
                        continue;
73 866
                    }
74
75
                    try {
76 64
                        // Use @ to try to ignore deprecations
77 64
                        @$object->{$property} = $data[$property];
78 64
                        unset($data[$property]);
79 62
                    } catch (\Throwable $e) {
80
                        if ($e::class === \TypeError::class) {
81
                            throw new MapperException(
82 16
                                \sprintf(
83
                                    "Can't hydrate %s.%s because property and value types are incompatible.",
84 16
                                    \get_parent_class($object),
85 8
                                    $property,
86
                                ),
87 8
                                previous: $e
88
                            );
89 8
                        }
90
                    }
91 8
                }
92
            }, null, $class)($object, $props, $data);
93 8
        }
94 8
    }
95 8
96 2
    /**
97 2
     * Map private relations of non-proxy entity
98
     */
99
    private function setRelationProperties(array $properties, RelationMap $relMap, object $object, array &$data): void
100 2
    {
101
        $refl = new \ReflectionClass($object);
102
        $setter = static function (object $object, array $props, array &$data) use ($refl, $relMap): void {
103
            foreach ($props as $property) {
104 6
                if (!\array_key_exists($property, $data)) {
105 6
                    continue;
106 6
                }
107
108
                $value = $data[$property];
109
110
                if ($value instanceof ReferenceInterface) {
111 16
                    $prop = $refl->getProperty($property);
112 16
113
                    if ($prop->hasType()) {
114
                        // todo: we can cache this
115
                        /** @var \ReflectionNamedType[] $types */
116
                        $types = $prop->getType() instanceof \ReflectionUnionType
117
                            ? $prop->getType()->getTypes()
118
                            : [$prop->getType()];
119
120
                        foreach ($types as $type) {
121
                            $c = $type->getName();
122
                            if ($c === 'object' || $value instanceof $c) {
123
                                $object->{$property} = $value;
124
                                unset($data[$property]);
125
126
                                // go to next property
127
                                continue 2;
128
                            }
129
                        }
130
131
                        $relation = $relMap->getRelations()[$property] ?? null;
132
                        if ($relation !== null) {
133
                            $value = $relation->collect($relation->resolve($value, true));
134
                        }
135
                    }
136
                }
137
138
                $object->{$property} = $value;
139
                unset($data[$property]);
140
            }
141
        };
142
143
        foreach ($properties as $class => $props) {
144
            if ($class === '') {
145
                // Hydrate public properties
146
                $setter($object, $props, $data);
147
                continue;
148
            }
149
150
            Closure::bind($setter, null, $class)($object, $props, $data);
151
        }
152
    }
153
}
154