|
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
EntityManagermight 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
EntityManageris closed. Any other code which depends on the same instance of theEntityManagerduring 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.