Completed
Push — master ( d4a3f1...5903a8 )
by Peter
02:29 queued 10s
created

ArrayHydrator::setHydrateId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
namespace pmill\Doctrine\Hydrator;
3
4
use Doctrine\DBAL\Types\Type;
5
use Doctrine\ORM\EntityManager;
6
use Doctrine\ORM\Mapping\ClassMetadataInfo;
7
use Exception;
8
9
class ArrayHydrator
10
{
11
    /**
12
     * The keys in the data array are entity field names
13
     */
14
    const HYDRATE_BY_FIELD = 1;
15
16
    /**
17
     * The keys in the data array are database column names
18
     */
19
    const HYDRATE_BY_COLUMN = 2;
20
21
    /**
22
     * @var EntityManager
23
     */
24
    protected $entityManager;
25
26
    /**
27
     * If true, then associations are filled only with reference proxies. This is faster than querying them from
28
     * database, but if the associated entity does not really exist, it will cause:
29
     * * The insert/update to fail, if there is a foreign key defined in database
30
     * * The record ind database also pointing to a non-existing record
31
     *
32
     * @var bool
33
     */
34
    protected $hydrateAssociationReferences = true;
35
36
    /**
37
     * Tells whether the input data array keys are entity field names or database column names
38
     *
39
     * @var int one of ArrayHydrator::HIDRATE_BY_* constants
40
     */
41
    protected $hydrateBy = self::HYDRATE_BY_FIELD;
42
43
    /**
44
     * If true, hydrate the primary key too. Useful if the primary key is not automatically generated by the database
45
     *
46
     * @var bool
47
     */
48
    protected $hydrateId = false;
49
50
    /**
51
     * @param EntityManager $entityManager
52
     */
53
    public function __construct(EntityManager $entityManager)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
54
    {
55
        $this->entityManager = $entityManager;
56
    }
57
58
    /**
59
     * @param $entity
60
     * @param array $data
61
     * @return mixed|object
62
     * @throws Exception
63
     */
64
    public function hydrate($entity, array $data)
65
    {
66
        if (is_string($entity) && class_exists($entity)) {
67
            $entity = new $entity;
68
        }
69
        elseif (!is_object($entity)) {
70
            throw new Exception('Entity passed to ArrayHydrator::hydrate() must be a class name or entity object');
71
        }
72
73
        $entity = $this->hydrateProperties($entity, $data);
74
        $entity = $this->hydrateAssociations($entity, $data);
75
        return $entity;
76
    }
77
78
    /**
79
     * @param boolean $hydrateAssociationReferences
80
     */
81
    public function setHydrateAssociationReferences($hydrateAssociationReferences)
82
    {
83
        $this->hydrateAssociationReferences = $hydrateAssociationReferences;
84
    }
85
86
    /**
87
     * @param bool $hydrateId
88
     */
89
    public function setHydrateId($hydrateId)
90
    {
91
        $this->hydrateId = $hydrateId;
92
    }
93
94
    /**
95
     * @param int $hydrateBy
96
     */
97
    public function setHydrateBy($hydrateBy)
98
    {
99
        $this->hydrateBy = $hydrateBy;
100
    }
101
102
    /**
103
     * @param object $entity the doctrine entity
104
     * @param array $data
105
     * @return object
106
     */
107
    protected function hydrateProperties($entity, $data)
108
    {
109
        $reflectionObject = new \ReflectionObject($entity);
110
111
        $metaData = $this->entityManager->getClassMetadata(get_class($entity));
112
        
113
        $platform = $this->entityManager->getConnection()
114
                                        ->getDatabasePlatform();
115
116
        $skipFields = $this->hydrateId ? [] : $metaData->identifier;
117
118
        foreach ($metaData->fieldNames as $fieldName) {
119
            $dataKey = $this->hydrateBy === self::HYDRATE_BY_FIELD ? $fieldName : $metaData->getColumnName($fieldName);
120
121
            if (isset($data[$dataKey]) && !in_array($fieldName, $skipFields, true)) {
122
                $value = $data[$dataKey];
123
124
                if (array_key_exists('type', $metaData->fieldMappings[$fieldName])) {
125
                    $fieldType = $metaData->fieldMappings[$fieldName]['type'];
126
127
                    $type = Type::getType($fieldType);
128
129
                    $value = $type->convertToPHPValue($value, $platform);
130
                }
131
132
                $entity = $this->setProperty($entity, $fieldName, $value, $reflectionObject);
133
            }
134
        }
135
136
        return $entity;
137
    }
138
139
    /**
140
     * @param $entity
141
     * @param $data
142
     * @return mixed
143
     */
144
    protected function hydrateAssociations($entity, $data)
145
    {
146
        $metaData = $this->entityManager->getClassMetadata(get_class($entity));
147
        foreach ($metaData->associationMappings as $fieldName => $mapping) {
148
            $associationData = $this->getAssociatedId($fieldName, $mapping, $data);
149
            if (!empty($associationData)) {
150 View Code Duplication
                if (in_array($mapping['type'], [ClassMetadataInfo::ONE_TO_ONE, ClassMetadataInfo::MANY_TO_ONE])) {
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...
151
                    $entity = $this->hydrateToOneAssociation($entity, $fieldName, $mapping, $associationData);
152
                }
153
154 View Code Duplication
                if (in_array($mapping['type'], [ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::MANY_TO_MANY])) {
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...
155
                    $entity = $this->hydrateToManyAssociation($entity, $fieldName, $mapping, $associationData);
156
                }
157
            }
158
        }
159
160
        return $entity;
161
    }
162
163
    /**
164
     * Retrieves the associated entity's id from $data
165
     *
166
     * @param string $fieldName name of field that stores the associated entity
167
     * @param array $mapping doctrine's association mapping array for the field
168
     * @param array $data the hydration data
169
     *
170
     * @return mixed null, if the association is not found
171
     */
172
    protected function getAssociatedId($fieldName, $mapping, $data)
173
    {
174
        if ($this->hydrateBy === self::HYDRATE_BY_FIELD) {
175
176
            return isset($data[$fieldName]) ? $data[$fieldName] : null;
177
        }
178
179
        // from this point it is self::HYDRATE_BY_COLUMN
180
        if (isset($mapping['joinColumns'])) {
181
            // we do not support compound foreign keys (yet)
182
            $columnName = $mapping['joinColumns'][0]['name'];
183
184
            return isset($data[$columnName]) ? $data[$columnName] : null;
185
        }
186
187
        // If joinColumns does not exist, then this is not the owning side of an association
188
        // This should not happen with column based hydration
189
        return null;
190
    }
191
192
    /**
193
     * @param $entity
194
     * @param $propertyName
195
     * @param $mapping
196
     * @param $value
197
     * @return mixed
198
     */
199
    protected function hydrateToOneAssociation($entity, $propertyName, $mapping, $value)
200
    {
201
        $reflectionObject = new \ReflectionObject($entity);
202
203
        $toOneAssociationObject = $this->fetchAssociationEntity($mapping['targetEntity'], $value);
204
        if (!is_null($toOneAssociationObject)) {
205
            $entity = $this->setProperty($entity, $propertyName, $toOneAssociationObject, $reflectionObject);
206
        }
207
208
        return $entity;
209
    }
210
211
    /**
212
     * @param $entity
213
     * @param $propertyName
214
     * @param $mapping
215
     * @param $value
216
     * @return mixed
217
     */
218
    protected function hydrateToManyAssociation($entity, $propertyName, $mapping, $value)
219
    {
220
        $reflectionObject = new \ReflectionObject($entity);
221
        $values = is_array($value) ? $value : [$value];
222
223
        $assocationObjects = [];
224
        foreach ($values as $value) {
225
            if (is_array($value)) {
226
                $assocationObjects[] = $this->hydrate($mapping['targetEntity'], $value);
227
            }
228
            elseif ($associationObject = $this->fetchAssociationEntity($mapping['targetEntity'], $value)) {
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $associationObject is correct as $this->fetchAssociationE...targetEntity'], $value) (which targets pmill\Doctrine\Hydrator\...etchAssociationEntity()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
229
                $assocationObjects[] = $associationObject;
230
            }
231
        }
232
233
        $entity = $this->setProperty($entity, $propertyName, $assocationObjects, $reflectionObject);
234
235
        return $entity;
236
    }
237
238
    /**
239
     * @param $entity
240
     * @param $propertyName
241
     * @param $value
242
     * @param null $reflectionObject
243
     * @return mixed
244
     */
245
    protected function setProperty($entity, $propertyName, $value, $reflectionObject = null)
246
    {
247
        $reflectionObject = is_null($reflectionObject) ? new \ReflectionObject($entity) : $reflectionObject;
248
        $property = $reflectionObject->getProperty($propertyName);
249
        $property->setAccessible(true);
250
        $property->setValue($entity, $value);
251
        return $entity;
252
    }
253
254
    /**
255
     * @param $className
256
     * @param $id
257
     * @return bool|\Doctrine\Common\Proxy\Proxy|null|object
258
     * @throws \Doctrine\ORM\ORMException
259
     * @throws \Doctrine\ORM\OptimisticLockException
260
     * @throws \Doctrine\ORM\TransactionRequiredException
261
     */
262
    protected function fetchAssociationEntity($className, $id)
263
    {
264
        if ($this->hydrateAssociationReferences) {
265
            return $this->entityManager->getReference($className, $id);
266
        }
267
268
        return $this->entityManager->find($className, $id);
269
    }
270
}
271