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
|
|
|
* @var EntityManager |
13
|
|
|
*/ |
14
|
|
|
protected $entityManager; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* @var bool |
18
|
|
|
*/ |
19
|
|
|
protected $hydrateAssociationReferences = true; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* @param EntityManager $entityManager |
23
|
|
|
*/ |
24
|
|
|
public function __construct(EntityManager $entityManager) |
|
|
|
|
25
|
|
|
{ |
26
|
|
|
$this->entityManager = $entityManager; |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @param $entity |
31
|
|
|
* @param array $data |
32
|
|
|
* @return mixed|object |
33
|
|
|
* @throws Exception |
34
|
|
|
*/ |
35
|
|
|
public function hydrate($entity, array $data) |
36
|
|
|
{ |
37
|
|
|
if (is_string($entity) && class_exists($entity)) { |
38
|
|
|
$entity = new $entity; |
39
|
|
|
} |
40
|
|
|
elseif (!is_object($entity)) { |
41
|
|
|
throw new Exception('Entity passed to ArrayHydrator::hydrate() must be a class name or entity object'); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
$entity = $this->hydrateProperties($entity, $data); |
45
|
|
|
$entity = $this->hydrateAssociations($entity, $data); |
46
|
|
|
return $entity; |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @param boolean $hydrateAssociationReferences |
51
|
|
|
*/ |
52
|
|
|
public function setHydrateAssociationReferences($hydrateAssociationReferences) |
53
|
|
|
{ |
54
|
|
|
$this->hydrateAssociationReferences = $hydrateAssociationReferences; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* @param $entity |
59
|
|
|
* @param $data |
60
|
|
|
* @return object |
61
|
|
|
*/ |
62
|
|
|
protected function hydrateProperties($entity, $data) |
63
|
|
|
{ |
64
|
|
|
$reflectionObject = new \ReflectionObject($entity); |
65
|
|
|
|
66
|
|
|
$metaData = $this->entityManager->getClassMetadata(get_class($entity)); |
67
|
|
|
|
68
|
|
|
$platform = $this->entityManager->getConnection() |
69
|
|
|
->getDatabasePlatform(); |
70
|
|
|
|
71
|
|
|
foreach ($metaData->columnNames as $propertyName) { |
72
|
|
|
if (isset($data[$propertyName]) && !in_array($propertyName, $metaData->identifier)) { |
73
|
|
|
$value = $data[$propertyName]; |
74
|
|
|
|
75
|
|
|
if (array_key_exists('type', $metaData->fieldMappings[$propertyName])) { |
76
|
|
|
$fieldType = $metaData->fieldMappings[$propertyName]['type']; |
77
|
|
|
|
78
|
|
|
$type = Type::getType($fieldType); |
79
|
|
|
|
80
|
|
|
$value = $type->convertToPHPValue($value, $platform); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
$entity = $this->setProperty($entity, $propertyName, $value, $reflectionObject); |
84
|
|
|
} |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
return $entity; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
|
|
* @param $entity |
92
|
|
|
* @param $data |
93
|
|
|
* @return mixed |
94
|
|
|
*/ |
95
|
|
|
protected function hydrateAssociations($entity, $data) |
96
|
|
|
{ |
97
|
|
|
$metaData = $this->entityManager->getClassMetadata(get_class($entity)); |
98
|
|
|
foreach ($metaData->associationMappings as $propertyName => $mapping) { |
99
|
|
|
if (isset($data[$propertyName])) { |
100
|
|
View Code Duplication |
if (in_array($mapping['type'], [ClassMetadataInfo::ONE_TO_ONE, ClassMetadataInfo::MANY_TO_ONE])) { |
|
|
|
|
101
|
|
|
$entity = $this->hydrateToOneAssociation($entity, $propertyName, $mapping, $data[$propertyName]); |
102
|
|
|
} |
103
|
|
|
|
104
|
|
View Code Duplication |
if (in_array($mapping['type'], [ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::MANY_TO_MANY])) { |
|
|
|
|
105
|
|
|
$entity = $this->hydrateToManyAssociation($entity, $propertyName, $mapping, $data[$propertyName]); |
106
|
|
|
} |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
return $entity; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @param $entity |
115
|
|
|
* @param $propertyName |
116
|
|
|
* @param $mapping |
117
|
|
|
* @param $value |
118
|
|
|
* @return mixed |
119
|
|
|
*/ |
120
|
|
|
protected function hydrateToOneAssociation($entity, $propertyName, $mapping, $value) |
121
|
|
|
{ |
122
|
|
|
$reflectionObject = new \ReflectionObject($entity); |
123
|
|
|
|
124
|
|
|
$toOneAssociationObject = $this->fetchAssociationEntity($mapping['targetEntity'], $value); |
125
|
|
|
if (!is_null($toOneAssociationObject)) { |
126
|
|
|
$entity = $this->setProperty($entity, $propertyName, $toOneAssociationObject, $reflectionObject); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
return $entity; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param $entity |
134
|
|
|
* @param $propertyName |
135
|
|
|
* @param $mapping |
136
|
|
|
* @param $value |
137
|
|
|
* @return mixed |
138
|
|
|
*/ |
139
|
|
|
protected function hydrateToManyAssociation($entity, $propertyName, $mapping, $value) |
140
|
|
|
{ |
141
|
|
|
$reflectionObject = new \ReflectionObject($entity); |
142
|
|
|
$values = is_array($value) ? $value : [$value]; |
143
|
|
|
|
144
|
|
|
$assocationObjects = []; |
145
|
|
|
foreach ($values as $value) { |
146
|
|
|
if (is_array($value)) { |
147
|
|
|
$assocationObjects[] = $this->hydrate($mapping['targetEntity'], $value); |
148
|
|
|
} |
149
|
|
|
elseif ($associationObject = $this->fetchAssociationEntity($mapping['targetEntity'], $value)) { |
|
|
|
|
150
|
|
|
$assocationObjects[] = $associationObject; |
151
|
|
|
} |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$entity = $this->setProperty($entity, $propertyName, $assocationObjects, $reflectionObject); |
155
|
|
|
|
156
|
|
|
return $entity; |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* @param $entity |
161
|
|
|
* @param $propertyName |
162
|
|
|
* @param $value |
163
|
|
|
* @param null $reflectionObject |
164
|
|
|
* @return mixed |
165
|
|
|
*/ |
166
|
|
|
protected function setProperty($entity, $propertyName, $value, $reflectionObject = null) |
167
|
|
|
{ |
168
|
|
|
$reflectionObject = is_null($reflectionObject) ? new \ReflectionObject($entity) : $reflectionObject; |
169
|
|
|
$property = $reflectionObject->getProperty($propertyName); |
170
|
|
|
$property->setAccessible(true); |
171
|
|
|
$property->setValue($entity, $value); |
172
|
|
|
return $entity; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* @param $className |
177
|
|
|
* @param $id |
178
|
|
|
* @return bool|\Doctrine\Common\Proxy\Proxy|null|object |
179
|
|
|
* @throws \Doctrine\ORM\ORMException |
180
|
|
|
* @throws \Doctrine\ORM\OptimisticLockException |
181
|
|
|
* @throws \Doctrine\ORM\TransactionRequiredException |
182
|
|
|
*/ |
183
|
|
|
protected function fetchAssociationEntity($className, $id) |
184
|
|
|
{ |
185
|
|
|
if ($this->hydrateAssociationReferences) { |
186
|
|
|
return $this->entityManager->getReference($className, $id); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
return $this->entityManager->find($className, $id); |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
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:If that code throws an exception and the
EntityManager
is closed. Any other code which depends on the same instance of theEntityManager
during this request will fail.On the other hand, if you instead inject the
ManagerRegistry
, thegetManager()
method guarantees that you will always get a usable manager instance.