Passed
Push — master ( 491d8e...9b8ef9 )
by Gerrit
03:23
created

EntityHydrator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
/**
3
 * Copyright (C) 2018 Gerrit Addiks.
4
 * This package (including this file) was released under the terms of the GPL-3.0.
5
 * You should have received a copy of the GNU General Public License along with this program.
6
 * If not, see <http://www.gnu.org/licenses/> or send me a mail so i can send you a copy.
7
 * @license GPL-3.0
8
 * @author Gerrit Addiks <[email protected]>
9
 */
10
11
namespace Addiks\RDMBundle\Hydration;
12
13
use ReflectionClass;
14
use ReflectionProperty;
15
use Doctrine\Common\Util\ClassUtils;
16
use Addiks\RDMBundle\Mapping\Drivers\MappingDriverInterface;
17
use Addiks\RDMBundle\Mapping\EntityMappingInterface;
18
use Addiks\RDMBundle\Mapping\MappingInterface;
19
use Addiks\RDMBundle\Hydration\EntityHydratorInterface;
20
use Addiks\RDMBundle\DataLoader\DataLoaderInterface;
21
use Doctrine\ORM\EntityManagerInterface;
22
use Addiks\RDMBundle\Hydration\HydrationContext;
23
use Webmozart\Assert\Assert;
24
use Exception;
25
use Throwable;
26
use Doctrine\ORM\Mapping\MappingException;
27
28
final class EntityHydrator implements EntityHydratorInterface
29
{
30
31
    /**
32
     * @var MappingDriverInterface
33
     */
34
    private $mappingDriver;
35
36
    /**
37
     * @var DataLoaderInterface
38
     */
39
    private $dbalDataLoader;
40
41 5
    public function __construct(
42
        MappingDriverInterface $mappingDriver,
43
        DataLoaderInterface $dbalDataLoader
44
    ) {
45 5
        $this->mappingDriver = $mappingDriver;
46 5
        $this->dbalDataLoader = $dbalDataLoader;
47 5
    }
48
49 3
    public function hydrateEntity($entity, EntityManagerInterface $entityManager): void
50
    {
51
        /** @var array<string> $dataFromAdditionalColumns */
52 3
        $dataFromAdditionalColumns = $this->dbalDataLoader->loadDBALDataForEntity(
53 3
            $entity,
54
            $entityManager
55
        );
56
57
        /** @var class-string $className */
0 ignored issues
show
Documentation introduced by
The doc-type class-string could not be parsed: Unknown type name "class-string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
58 3
        $className = get_class($entity);
59
60
        do {
61 3
            if (class_exists(ClassUtils::class)) {
62 3
                $className = ClassUtils::getRealClass($className);
63 3
                Assert::classExists($className);
64
            }
65
66 3
            $classReflection = new ReflectionClass($className);
67
68
            /** @var ?EntityMappingInterface $mapping */
0 ignored issues
show
Documentation introduced by
The doc-type ?EntityMappingInterface could not be parsed: Unknown type name "?EntityMappingInterface" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
69 3
            $mapping = $this->mappingDriver->loadRDMMetadataForClass($className);
70
71 3
            if ($mapping instanceof EntityMappingInterface) {
72
                /** @var string $processDescription */
73 3
                $processDescription = sprintf("of entity '%s'", $className);
74
75
                try {
76 3 View Code Duplication
                    if ($mapping instanceof EntityMappingInterface) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
77 3
                        $context = new HydrationContext($entity, $entityManager);
78
79 3
                        foreach ($mapping->getFieldMappings() as $fieldName => $fieldMapping) {
80
                            /** @var MappingInterface $fieldMapping */
81
82 3
                            $processDescription = sprintf(
83 3
                                "of field '%s' of entity '%s'",
84
                                $fieldName,
85
                                $className
86
                            );
87
88
                            /** @var mixed $value */
89 3
                            $value = $fieldMapping->resolveValue(
90 3
                                $context,
91
                                $dataFromAdditionalColumns
92
                            );
93
94
                            /** @var ReflectionClass $concreteClassReflection */
95 3
                            $concreteClassReflection = $classReflection;
96
97 3
                            while (!$concreteClassReflection->hasProperty($fieldName)) {
98
                                $concreteClassReflection = $concreteClassReflection->getParentClass();
99
100
                                Assert::object($concreteClassReflection, sprintf(
101
                                    "Property '%s' does not exist on object of class '%s'!",
102
                                    $fieldName,
103
                                    $className
104
                                ));
105
                            }
106
107
                            /** @var ReflectionProperty $propertyReflection */
108 3
                            $propertyReflection = $concreteClassReflection->getProperty($fieldName);
109
110 3
                            $propertyReflection->setAccessible(true);
111 3
                            $propertyReflection->setValue($entity, $value);
112
                        }
113
                    }
114
115
                } catch (Throwable $exception) {
116
                    throw new MappingException(sprintf(
117
                        "Exception during hydration %s: '%s'!",
118
                        $processDescription,
119
                        $exception->getMessage()
120
                    ), 0, $exception);
121
                }
122
            }
123
124 3
            $className = current(class_parents($className));
125 3
        } while (class_exists($className));
126 3
    }
127
128 3
    public function assertHydrationOnEntity($entity, EntityManagerInterface $entityManager): void
129
    {
130
        /** @var array<string> $dataFromAdditionalColumns */
131 3
        $dataFromAdditionalColumns = $this->dbalDataLoader->loadDBALDataForEntity(
132 3
            $entity,
133
            $entityManager
134
        );
135
136
        /** @var class-string $className */
0 ignored issues
show
Documentation introduced by
The doc-type class-string could not be parsed: Unknown type name "class-string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
137 3
        $className = get_class($entity);
138
139
        do {
140 3
            if (class_exists(ClassUtils::class)) {
141 3
                $className = ClassUtils::getRealClass($className);
142 3
                Assert::classExists($className);
143
            }
144
145 3
            $classReflection = new ReflectionClass($className);
146
147
            /** @var ?EntityMappingInterface $mapping */
0 ignored issues
show
Documentation introduced by
The doc-type ?EntityMappingInterface could not be parsed: Unknown type name "?EntityMappingInterface" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
148 3
            $mapping = $this->mappingDriver->loadRDMMetadataForClass($className);
149
150 3
            if ($mapping instanceof EntityMappingInterface) {
151 3 View Code Duplication
                if ($mapping instanceof EntityMappingInterface) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
152 3
                    $context = new HydrationContext($entity, $entityManager);
153
154 3
                    foreach ($mapping->getFieldMappings() as $fieldName => $fieldMapping) {
155
                        /** @var MappingInterface $fieldMapping */
156
157
                        /** @var ReflectionClass $concreteClassReflection */
158 3
                        $concreteClassReflection = $classReflection;
159
160 3
                        while (!$concreteClassReflection->hasProperty($fieldName)) {
161
                            $concreteClassReflection = $concreteClassReflection->getParentClass();
162
163
                            Assert::notFalse($concreteClassReflection, sprintf(
164
                                "Property '%s' does not exist on object of class '%s'!",
165
                                $fieldName,
166
                                $className
167
                            ));
168
                        }
169
170
                        /** @var ReflectionProperty $propertyReflection */
171 3
                        $propertyReflection = $concreteClassReflection->getProperty($fieldName);
172
173 3
                        $propertyReflection->setAccessible(true);
174
175
                        /** @var object|null $actualValue */
176 3
                        $actualValue = null;
177
178 3
                        if ($propertyReflection->isInitialized($entity)) {
179 3
                            $actualValue = $propertyReflection->getValue($entity);
180
                        }
181
182 3
                        $fieldMapping->assertValue(
183 3
                            $context,
184
                            $dataFromAdditionalColumns,
185
                            $actualValue
186
                        );
187
                    }
188
                }
189
            }
190
191 2
            $className = current(class_parents($className));
192 2
        } while (class_exists($className));
193 2
    }
194
195
}
196