1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace DoctrineModule\Stdlib\Hydrator; |
4
|
|
|
|
5
|
|
|
use DateTime; |
6
|
|
|
use Doctrine\Common\Persistence\Mapping\ClassMetadata; |
7
|
|
|
use Doctrine\Common\Persistence\ObjectManager; |
8
|
|
|
use Doctrine\Common\Inflector\Inflector; |
9
|
|
|
use InvalidArgumentException; |
10
|
|
|
use RuntimeException; |
11
|
|
|
use Traversable; |
12
|
|
|
use Zend\Stdlib\ArrayUtils; |
13
|
|
|
use Zend\Hydrator\AbstractHydrator; |
14
|
|
|
use Zend\Hydrator\Filter\FilterProviderInterface; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* This hydrator has been completely refactored for DoctrineModule 0.7.0. It provides an easy and powerful way |
18
|
|
|
* of extracting/hydrator objects in Doctrine, by handling most associations types. |
19
|
|
|
* |
20
|
|
|
* Starting from DoctrineModule 0.8.0, the hydrator can be used multiple times with different objects |
21
|
|
|
* |
22
|
|
|
* @license MIT |
23
|
|
|
* @link http://www.doctrine-project.org/ |
24
|
|
|
* @since 0.7.0 |
25
|
|
|
* @author Michael Gallego <[email protected]> |
26
|
|
|
*/ |
27
|
|
|
class DoctrineObject extends AbstractHydrator |
28
|
|
|
{ |
29
|
|
|
/** |
30
|
|
|
* @var ObjectManager |
31
|
|
|
*/ |
32
|
|
|
protected $objectManager; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var ClassMetadata |
36
|
|
|
*/ |
37
|
|
|
protected $metadata; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var bool |
41
|
|
|
*/ |
42
|
|
|
protected $byValue = true; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var string |
46
|
|
|
*/ |
47
|
|
|
protected $defaultByValueStrategy = __NAMESPACE__ . '\Strategy\AllowRemoveByValue'; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var string |
51
|
|
|
*/ |
52
|
|
|
protected $defaultByReferenceStrategy = __NAMESPACE__ . '\Strategy\AllowRemoveByReference'; |
53
|
|
|
|
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Constructor |
57
|
|
|
* |
58
|
|
|
* @param ObjectManager $objectManager The ObjectManager to use |
59
|
|
|
* @param bool $byValue If set to true, hydrator will always use entity's public API |
60
|
|
|
*/ |
61
|
73 |
|
public function __construct(ObjectManager $objectManager, $byValue = true) |
62
|
|
|
{ |
63
|
73 |
|
parent::__construct(); |
64
|
|
|
|
65
|
73 |
|
$this->objectManager = $objectManager; |
66
|
73 |
|
$this->byValue = (bool) $byValue; |
67
|
73 |
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @return string |
71
|
|
|
*/ |
72
|
14 |
|
public function getDefaultByValueStrategy() |
73
|
|
|
{ |
74
|
14 |
|
return $this->defaultByValueStrategy; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* @param string $defaultByValueStrategy |
79
|
|
|
* @return DoctrineObject |
80
|
|
|
*/ |
81
|
1 |
|
public function setDefaultByValueStrategy($defaultByValueStrategy) |
82
|
|
|
{ |
83
|
1 |
|
$this->defaultByValueStrategy = $defaultByValueStrategy; |
84
|
1 |
|
return $this; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @return string |
89
|
|
|
*/ |
90
|
9 |
|
public function getDefaultByReferenceStrategy() |
91
|
|
|
{ |
92
|
9 |
|
return $this->defaultByReferenceStrategy; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @param string $defaultByReferenceStrategy |
97
|
|
|
* @return DoctrineObject |
98
|
|
|
*/ |
99
|
1 |
|
public function setDefaultByReferenceStrategy($defaultByReferenceStrategy) |
100
|
|
|
{ |
101
|
1 |
|
$this->defaultByReferenceStrategy = $defaultByReferenceStrategy; |
102
|
1 |
|
return $this; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* Extract values from an object |
107
|
|
|
* |
108
|
|
|
* @param object $object |
109
|
|
|
* @return array |
110
|
|
|
*/ |
111
|
17 |
|
public function extract($object) |
112
|
|
|
{ |
113
|
17 |
|
$this->prepare($object); |
114
|
|
|
|
115
|
17 |
|
if ($this->byValue) { |
116
|
10 |
|
return $this->extractByValue($object); |
117
|
|
|
} |
118
|
|
|
|
119
|
7 |
|
return $this->extractByReference($object); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Hydrate $object with the provided $data. |
124
|
|
|
* |
125
|
|
|
* @param array $data |
126
|
|
|
* @param object $object |
127
|
|
|
* @return object |
128
|
|
|
*/ |
129
|
57 |
|
public function hydrate(array $data, $object) |
130
|
|
|
{ |
131
|
57 |
|
$this->prepare($object); |
132
|
|
|
|
133
|
57 |
|
if ($this->byValue) { |
134
|
43 |
|
return $this->hydrateByValue($data, $object); |
135
|
|
|
} |
136
|
|
|
|
137
|
29 |
|
return $this->hydrateByReference($data, $object); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Prepare the hydrator by adding strategies to every collection valued associations |
142
|
|
|
* |
143
|
|
|
* @param object $object |
144
|
|
|
* @return void |
145
|
|
|
*/ |
146
|
73 |
|
protected function prepare($object) |
147
|
|
|
{ |
148
|
73 |
|
$this->metadata = $this->objectManager->getClassMetadata(get_class($object)); |
149
|
73 |
|
$this->prepareStrategies(); |
150
|
73 |
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Prepare strategies before the hydrator is used |
154
|
|
|
* |
155
|
|
|
* @throws \InvalidArgumentException |
156
|
|
|
* @return void |
157
|
|
|
*/ |
158
|
73 |
|
protected function prepareStrategies() |
159
|
|
|
{ |
160
|
73 |
|
$associations = $this->metadata->getAssociationNames(); |
161
|
|
|
|
162
|
73 |
|
foreach ($associations as $association) { |
163
|
37 |
|
if ($this->metadata->isCollectionValuedAssociation($association)) { |
164
|
|
|
// Add a strategy if the association has none set by user |
165
|
23 |
|
if (! $this->hasStrategy($association)) { |
166
|
21 |
|
if ($this->byValue) { |
167
|
14 |
|
$strategyClassName = $this->getDefaultByValueStrategy(); |
168
|
|
|
} else { |
169
|
9 |
|
$strategyClassName = $this->getDefaultByReferenceStrategy(); |
170
|
|
|
} |
171
|
|
|
|
172
|
21 |
|
$this->addStrategy($association, new $strategyClassName()); |
173
|
|
|
} |
174
|
|
|
|
175
|
23 |
|
$strategy = $this->getStrategy($association); |
176
|
|
|
|
177
|
23 |
|
if (! $strategy instanceof Strategy\AbstractCollectionStrategy) { |
178
|
|
|
throw new InvalidArgumentException( |
179
|
|
|
sprintf( |
180
|
|
|
'Strategies used for collections valued associations must inherit from ' |
181
|
|
|
. 'Strategy\AbstractCollectionStrategy, %s given', |
182
|
|
|
get_class($strategy) |
183
|
|
|
) |
184
|
|
|
); |
185
|
|
|
} |
186
|
|
|
|
187
|
23 |
|
$strategy->setCollectionName($association) |
188
|
37 |
|
->setClassMetadata($this->metadata); |
189
|
|
|
} |
190
|
|
|
} |
191
|
73 |
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Extract values from an object using a by-value logic (this means that it uses the entity |
195
|
|
|
* API, in this case, getters) |
196
|
|
|
* |
197
|
|
|
* @param object $object |
198
|
|
|
* @throws RuntimeException |
199
|
|
|
* @return array |
200
|
|
|
*/ |
201
|
10 |
|
protected function extractByValue($object) |
202
|
|
|
{ |
203
|
10 |
|
$fieldNames = array_merge($this->metadata->getFieldNames(), $this->metadata->getAssociationNames()); |
204
|
10 |
|
$methods = get_class_methods($object); |
205
|
10 |
|
$filter = $object instanceof FilterProviderInterface |
206
|
|
|
? $object->getFilter() |
207
|
10 |
|
: $this->filterComposite; |
208
|
|
|
|
209
|
10 |
|
$data = []; |
210
|
10 |
|
foreach ($fieldNames as $fieldName) { |
211
|
10 |
|
if ($filter && ! $filter->filter($fieldName)) { |
212
|
1 |
|
continue; |
213
|
|
|
} |
214
|
|
|
|
215
|
10 |
|
$getter = 'get' . Inflector::classify($fieldName); |
216
|
10 |
|
$isser = 'is' . Inflector::classify($fieldName); |
217
|
|
|
|
218
|
10 |
|
$dataFieldName = $this->computeExtractFieldName($fieldName); |
219
|
10 |
|
if (in_array($getter, $methods)) { |
220
|
10 |
|
$data[$dataFieldName] = $this->extractValue($fieldName, $object->$getter(), $object); |
221
|
1 |
|
} elseif (in_array($isser, $methods)) { |
222
|
1 |
|
$data[$dataFieldName] = $this->extractValue($fieldName, $object->$isser(), $object); |
223
|
|
|
} elseif (substr($fieldName, 0, 2) === 'is' |
224
|
|
|
&& ctype_upper(substr($fieldName, 2, 1)) |
225
|
|
|
&& in_array($fieldName, $methods)) { |
226
|
10 |
|
$data[$dataFieldName] = $this->extractValue($fieldName, $object->$fieldName(), $object); |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
// Unknown fields are ignored |
230
|
|
|
} |
231
|
|
|
|
232
|
10 |
|
return $data; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Extract values from an object using a by-reference logic (this means that values are |
237
|
|
|
* directly fetched without using the public API of the entity, in this case, getters) |
238
|
|
|
* |
239
|
|
|
* @param object $object |
240
|
|
|
* @return array |
241
|
|
|
*/ |
242
|
7 |
|
protected function extractByReference($object) |
243
|
|
|
{ |
244
|
7 |
|
$fieldNames = array_merge($this->metadata->getFieldNames(), $this->metadata->getAssociationNames()); |
245
|
7 |
|
$refl = $this->metadata->getReflectionClass(); |
246
|
7 |
|
$filter = $object instanceof FilterProviderInterface |
247
|
|
|
? $object->getFilter() |
248
|
7 |
|
: $this->filterComposite; |
249
|
|
|
|
250
|
7 |
|
$data = []; |
251
|
7 |
|
foreach ($fieldNames as $fieldName) { |
252
|
7 |
|
if ($filter && ! $filter->filter($fieldName)) { |
253
|
1 |
|
continue; |
254
|
|
|
} |
255
|
7 |
|
$reflProperty = $refl->getProperty($fieldName); |
256
|
7 |
|
$reflProperty->setAccessible(true); |
257
|
|
|
|
258
|
7 |
|
$dataFieldName = $this->computeExtractFieldName($fieldName); |
259
|
7 |
|
$data[$dataFieldName] = $this->extractValue($fieldName, $reflProperty->getValue($object), $object); |
260
|
|
|
} |
261
|
|
|
|
262
|
7 |
|
return $data; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Converts a value for hydration |
267
|
|
|
* Apply strategies first, then the type conversions |
268
|
|
|
* |
269
|
|
|
* @inheritdoc |
270
|
|
|
*/ |
271
|
54 |
|
public function hydrateValue($name, $value, $data = null) |
272
|
|
|
{ |
273
|
54 |
|
$value = parent::hydrateValue($name, $value, $data); |
274
|
|
|
|
275
|
54 |
|
if (is_null($value) && $this->isNullable($name)) { |
276
|
|
|
return null; |
277
|
|
|
} |
278
|
|
|
|
279
|
54 |
|
return $this->handleTypeConversions($value, $this->metadata->getTypeOfField($name)); |
280
|
|
|
} |
281
|
|
|
|
282
|
|
|
/** |
283
|
|
|
* Hydrate the object using a by-value logic (this means that it uses the entity API, in this |
284
|
|
|
* case, setters) |
285
|
|
|
* |
286
|
|
|
* @param array $data |
287
|
|
|
* @param object $object |
288
|
|
|
* @throws RuntimeException |
289
|
|
|
* @return object |
290
|
|
|
*/ |
291
|
44 |
|
protected function hydrateByValue(array $data, $object) |
292
|
|
|
{ |
293
|
44 |
|
$tryObject = $this->tryConvertArrayToObject($data, $object); |
294
|
44 |
|
$metadata = $this->metadata; |
295
|
|
|
|
296
|
44 |
|
if (is_object($tryObject)) { |
297
|
41 |
|
$object = $tryObject; |
298
|
|
|
} |
299
|
|
|
|
300
|
44 |
|
foreach ($data as $field => $value) { |
301
|
42 |
|
$field = $this->computeHydrateFieldName($field); |
302
|
42 |
|
$setter = 'set' . Inflector::classify($field); |
303
|
|
|
|
304
|
42 |
|
if ($metadata->hasAssociation($field)) { |
305
|
18 |
|
$target = $metadata->getAssociationTargetClass($field); |
306
|
|
|
|
307
|
18 |
|
if ($metadata->isSingleValuedAssociation($field)) { |
308
|
7 |
|
if (! is_callable([$object, $setter])) { |
309
|
|
|
continue; |
310
|
|
|
} |
311
|
|
|
|
312
|
7 |
|
$value = $this->toOne($target, $this->hydrateValue($field, $value, $data)); |
313
|
|
|
|
314
|
7 |
|
if (null === $value |
315
|
7 |
|
&& ! current($metadata->getReflectionClass()->getMethod($setter)->getParameters())->allowsNull() |
316
|
|
|
) { |
317
|
1 |
|
continue; |
318
|
|
|
} |
319
|
|
|
|
320
|
6 |
|
$object->$setter($value); |
321
|
11 |
|
} elseif ($metadata->isCollectionValuedAssociation($field)) { |
322
|
17 |
|
$this->toMany($object, $field, $target, $value); |
323
|
|
|
} |
324
|
|
|
} else { |
325
|
27 |
|
if (! is_callable([$object, $setter])) { |
326
|
1 |
|
continue; |
327
|
|
|
} |
328
|
|
|
|
329
|
40 |
|
$object->$setter($this->hydrateValue($field, $value, $data)); |
330
|
|
|
} |
331
|
|
|
} |
332
|
|
|
|
333
|
44 |
|
return $object; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Hydrate the object using a by-reference logic (this means that values are modified directly without |
338
|
|
|
* using the public API, in this case setters, and hence override any logic that could be done in those |
339
|
|
|
* setters) |
340
|
|
|
* |
341
|
|
|
* @param array $data |
342
|
|
|
* @param object $object |
343
|
|
|
* @return object |
344
|
|
|
*/ |
345
|
30 |
|
protected function hydrateByReference(array $data, $object) |
346
|
|
|
{ |
347
|
30 |
|
$tryObject = $this->tryConvertArrayToObject($data, $object); |
348
|
30 |
|
$metadata = $this->metadata; |
349
|
30 |
|
$refl = $metadata->getReflectionClass(); |
350
|
|
|
|
351
|
30 |
|
if (is_object($tryObject)) { |
352
|
29 |
|
$object = $tryObject; |
353
|
|
|
} |
354
|
|
|
|
355
|
30 |
|
foreach ($data as $field => $value) { |
356
|
28 |
|
$field = $this->computeHydrateFieldName($field); |
357
|
|
|
|
358
|
|
|
// Ignore unknown fields |
359
|
28 |
|
if (! $refl->hasProperty($field)) { |
360
|
|
|
continue; |
361
|
|
|
} |
362
|
|
|
|
363
|
28 |
|
$reflProperty = $refl->getProperty($field); |
364
|
28 |
|
$reflProperty->setAccessible(true); |
365
|
|
|
|
366
|
28 |
|
if ($metadata->hasAssociation($field)) { |
367
|
11 |
|
$target = $metadata->getAssociationTargetClass($field); |
368
|
|
|
|
369
|
11 |
|
if ($metadata->isSingleValuedAssociation($field)) { |
370
|
5 |
|
$value = $this->toOne($target, $this->hydrateValue($field, $value, $data)); |
371
|
5 |
|
$reflProperty->setValue($object, $value); |
372
|
6 |
|
} elseif ($metadata->isCollectionValuedAssociation($field)) { |
373
|
11 |
|
$this->toMany($object, $field, $target, $value); |
374
|
|
|
} |
375
|
|
|
} else { |
376
|
28 |
|
$reflProperty->setValue($object, $this->hydrateValue($field, $value, $data)); |
377
|
|
|
} |
378
|
|
|
} |
379
|
|
|
|
380
|
30 |
|
return $object; |
381
|
|
|
} |
382
|
|
|
|
383
|
|
|
/** |
384
|
|
|
* This function tries, given an array of data, to convert it to an object if the given array contains |
385
|
|
|
* an identifier for the object. This is useful in a context of updating existing entities, without ugly |
386
|
|
|
* tricks like setting manually the existing id directly into the entity |
387
|
|
|
* |
388
|
|
|
* @param array $data The data that may contain identifiers keys |
389
|
|
|
* @param object $object |
390
|
|
|
* @return object |
391
|
|
|
*/ |
392
|
57 |
|
protected function tryConvertArrayToObject($data, $object) |
393
|
|
|
{ |
394
|
57 |
|
$metadata = $this->metadata; |
395
|
57 |
|
$identifierNames = $metadata->getIdentifierFieldNames($object); |
|
|
|
|
396
|
57 |
|
$identifierValues = []; |
397
|
|
|
|
398
|
57 |
|
if (empty($identifierNames)) { |
399
|
31 |
|
return $object; |
400
|
|
|
} |
401
|
|
|
|
402
|
26 |
|
foreach ($identifierNames as $identifierName) { |
403
|
26 |
|
if (! isset($data[$identifierName])) { |
404
|
22 |
|
return $object; |
405
|
|
|
} |
406
|
|
|
|
407
|
4 |
|
$identifierValues[$identifierName] = $data[$identifierName]; |
408
|
|
|
} |
409
|
|
|
|
410
|
4 |
|
return $this->find($identifierValues, $metadata->getName()); |
411
|
|
|
} |
412
|
|
|
|
413
|
|
|
/** |
414
|
|
|
* Handle ToOne associations |
415
|
|
|
* |
416
|
|
|
* When $value is an array but is not the $target's identifiers, $value is |
417
|
|
|
* most likely an array of fieldset data. The identifiers will be determined |
418
|
|
|
* and a target instance will be initialized and then hydrated. The hydrated |
419
|
|
|
* target will be returned. |
420
|
|
|
* |
421
|
|
|
* @param string $target |
422
|
|
|
* @param mixed $value |
423
|
|
|
* @return object |
424
|
|
|
*/ |
425
|
12 |
|
protected function toOne($target, $value) |
426
|
|
|
{ |
427
|
12 |
|
$metadata = $this->objectManager->getClassMetadata($target); |
428
|
|
|
|
429
|
12 |
|
if (is_array($value) && array_keys($value) != $metadata->getIdentifier()) { |
430
|
|
|
// $value is most likely an array of fieldset data |
431
|
1 |
|
$identifiers = array_intersect_key( |
432
|
1 |
|
$value, |
433
|
1 |
|
array_flip($metadata->getIdentifier()) |
434
|
|
|
); |
435
|
1 |
|
$object = $this->find($identifiers, $target) ?: new $target; |
436
|
1 |
|
return $this->hydrate($value, $object); |
437
|
|
|
} |
438
|
|
|
|
439
|
11 |
|
return $this->find($value, $target); |
440
|
|
|
} |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Handle ToMany associations. In proper Doctrine design, Collections should not be swapped, so |
444
|
|
|
* collections are always handled by reference. Internally, every collection is handled using specials |
445
|
|
|
* strategies that inherit from AbstractCollectionStrategy class, and that add or remove elements but without |
446
|
|
|
* changing the collection of the object |
447
|
|
|
* |
448
|
|
|
* @param object $object |
449
|
|
|
* @param mixed $collectionName |
450
|
|
|
* @param string $target |
451
|
|
|
* @param mixed $values |
452
|
|
|
* |
453
|
|
|
* @throws \InvalidArgumentException |
454
|
|
|
* |
455
|
|
|
* @return void |
456
|
|
|
*/ |
457
|
17 |
|
protected function toMany($object, $collectionName, $target, $values) |
458
|
|
|
{ |
459
|
17 |
|
$metadata = $this->objectManager->getClassMetadata(ltrim($target, '\\')); |
460
|
17 |
|
$identifier = $metadata->getIdentifier(); |
461
|
|
|
|
462
|
17 |
|
if (! is_array($values) && ! $values instanceof Traversable) { |
463
|
|
|
$values = (array)$values; |
464
|
|
|
} |
465
|
|
|
|
466
|
17 |
|
$collection = []; |
467
|
|
|
|
468
|
|
|
// If the collection contains identifiers, fetch the objects from database |
469
|
17 |
|
foreach ($values as $value) { |
470
|
17 |
|
if ($value instanceof $target) { |
471
|
|
|
// assumes modifications have already taken place in object |
472
|
7 |
|
$collection[] = $value; |
473
|
7 |
|
continue; |
474
|
|
|
} elseif (empty($value)) { |
475
|
|
|
// assumes no id and retrieves new $target |
476
|
1 |
|
$collection[] = $this->find($value, $target); |
477
|
1 |
|
continue; |
478
|
|
|
} |
479
|
|
|
|
480
|
9 |
|
$find = []; |
481
|
9 |
|
if (is_array($identifier)) { |
482
|
9 |
|
foreach ($identifier as $field) { |
483
|
9 |
|
switch (gettype($value)) { |
484
|
9 |
|
case 'object': |
485
|
1 |
|
$getter = 'get' . Inflector::classify($field); |
486
|
|
|
|
487
|
1 |
|
if (is_callable([$value, $getter])) { |
488
|
|
|
$find[$field] = $value->$getter(); |
489
|
1 |
|
} elseif (property_exists($value, $field)) { |
490
|
1 |
|
$find[$field] = $value->$field; |
491
|
|
|
} |
492
|
1 |
|
break; |
493
|
8 |
|
case 'array': |
494
|
5 |
|
if (array_key_exists($field, $value) && $value[$field] != null) { |
495
|
5 |
|
$find[$field] = $value[$field]; |
496
|
5 |
|
unset($value[$field]); // removed identifier from persistable data |
497
|
|
|
} |
498
|
5 |
|
break; |
499
|
|
|
default: |
500
|
3 |
|
$find[$field] = $value; |
501
|
9 |
|
break; |
502
|
|
|
} |
503
|
|
|
} |
504
|
|
|
} |
505
|
|
|
|
506
|
9 |
|
if (! empty($find) && $found = $this->find($find, $target)) { |
507
|
9 |
|
$collection[] = (is_array($value)) ? $this->hydrate($value, $found) : $found; |
508
|
|
|
} else { |
509
|
9 |
|
$collection[] = (is_array($value)) ? $this->hydrate($value, new $target) : new $target; |
510
|
|
|
} |
511
|
|
|
} |
512
|
|
|
|
513
|
17 |
|
$collection = array_filter( |
514
|
17 |
|
$collection, |
515
|
|
|
function ($item) { |
516
|
17 |
|
return null !== $item; |
517
|
17 |
|
} |
518
|
|
|
); |
519
|
|
|
|
520
|
|
|
// Set the object so that the strategy can extract the Collection from it |
521
|
|
|
|
522
|
|
|
/** @var \DoctrineModule\Stdlib\Hydrator\Strategy\AbstractCollectionStrategy $collectionStrategy */ |
523
|
17 |
|
$collectionStrategy = $this->getStrategy($collectionName); |
524
|
17 |
|
$collectionStrategy->setObject($object); |
525
|
|
|
|
526
|
|
|
// We could directly call hydrate method from the strategy, but if people want to override |
527
|
|
|
// hydrateValue function, they can do it and do their own stuff |
528
|
17 |
|
$this->hydrateValue($collectionName, $collection, $values); |
529
|
17 |
|
} |
530
|
|
|
|
531
|
|
|
/** |
532
|
|
|
* Handle various type conversions that should be supported natively by Doctrine (like DateTime) |
533
|
|
|
* See Documentation of Doctrine Mapping Types for defaults |
534
|
|
|
* |
535
|
|
|
* @link http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#doctrine-mapping-types |
536
|
|
|
* @param mixed $value |
537
|
|
|
* @param string $typeOfField |
538
|
|
|
* @return DateTime |
539
|
|
|
*/ |
540
|
54 |
|
protected function handleTypeConversions($value, $typeOfField) |
541
|
|
|
{ |
542
|
54 |
|
if (is_null($value)) { |
543
|
6 |
|
return null; |
544
|
|
|
} |
545
|
|
|
|
546
|
48 |
|
switch ($typeOfField) { |
547
|
48 |
|
case 'boolean': |
548
|
1 |
|
$value = (bool)$value; |
549
|
1 |
|
break; |
550
|
47 |
|
case 'string': |
551
|
38 |
|
case 'text': |
552
|
37 |
|
case 'bigint': |
553
|
36 |
|
case 'decimal': |
554
|
16 |
|
$value = (string)$value; |
555
|
16 |
|
break; |
556
|
35 |
|
case 'integer': |
557
|
32 |
|
case 'smallint': |
558
|
5 |
|
$value = (int)$value; |
559
|
5 |
|
break; |
560
|
31 |
|
case 'float': |
561
|
1 |
|
$value = (double)$value; |
562
|
1 |
|
break; |
563
|
30 |
|
case 'datetimetz': |
564
|
29 |
|
case 'datetime': |
565
|
26 |
|
case 'time': |
566
|
25 |
|
case 'date': |
567
|
6 |
|
if ($value === '') { |
568
|
1 |
|
return null; |
569
|
|
|
} |
570
|
|
|
|
571
|
5 |
|
if ($value instanceof Datetime) { |
572
|
4 |
|
return $value; |
573
|
|
|
} |
574
|
|
|
|
575
|
5 |
|
if (is_int($value)) { |
576
|
5 |
|
$dateTime = new DateTime(); |
577
|
5 |
|
$dateTime->setTimestamp($value); |
578
|
5 |
|
return $dateTime; |
579
|
|
|
} |
580
|
|
|
|
581
|
4 |
|
if (is_string($value)) { |
582
|
4 |
|
return new DateTime($value); |
583
|
|
|
} |
584
|
|
|
|
585
|
|
|
break; |
586
|
|
|
default: |
587
|
24 |
|
break; |
588
|
|
|
} |
589
|
|
|
|
590
|
42 |
|
return $value; |
|
|
|
|
591
|
|
|
} |
592
|
|
|
|
593
|
|
|
/** |
594
|
|
|
* Find an object by a given target class and identifier |
595
|
|
|
* |
596
|
|
|
* @param mixed $identifiers |
597
|
|
|
* @param string $targetClass |
598
|
|
|
* |
599
|
|
|
* @return object|null |
600
|
|
|
*/ |
601
|
26 |
|
protected function find($identifiers, $targetClass) |
602
|
|
|
{ |
603
|
26 |
|
if ($identifiers instanceof $targetClass) { |
604
|
2 |
|
return $identifiers; |
605
|
|
|
} |
606
|
|
|
|
607
|
24 |
|
if ($this->isNullIdentifier($identifiers)) { |
608
|
5 |
|
return null; |
609
|
|
|
} |
610
|
|
|
|
611
|
19 |
|
return $this->objectManager->find($targetClass, $identifiers); |
612
|
|
|
} |
613
|
|
|
|
614
|
|
|
/** |
615
|
|
|
* Verifies if a provided identifier is to be considered null |
616
|
|
|
* |
617
|
|
|
* @param mixed $identifier |
618
|
|
|
* |
619
|
|
|
* @return bool |
620
|
|
|
*/ |
621
|
24 |
|
private function isNullIdentifier($identifier) |
622
|
|
|
{ |
623
|
24 |
|
if (null === $identifier) { |
624
|
5 |
|
return true; |
625
|
|
|
} |
626
|
|
|
|
627
|
19 |
|
if ($identifier instanceof Traversable || is_array($identifier)) { |
628
|
16 |
|
$nonNullIdentifiers = array_filter( |
629
|
16 |
|
ArrayUtils::iteratorToArray($identifier), |
630
|
|
|
function ($value) { |
631
|
16 |
|
return null !== $value; |
632
|
16 |
|
} |
633
|
|
|
); |
634
|
|
|
|
635
|
16 |
|
return empty($nonNullIdentifiers); |
636
|
|
|
} |
637
|
|
|
|
638
|
3 |
|
return false; |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
/** |
642
|
|
|
* Check the field is nullable |
643
|
|
|
* |
644
|
|
|
* @param $name |
645
|
|
|
* @return bool |
646
|
|
|
*/ |
647
|
6 |
|
private function isNullable($name) |
648
|
|
|
{ |
649
|
|
|
//TODO: need update after updating isNullable method of Doctrine\ORM\Mapping\ClassMetadata |
650
|
6 |
|
if ($this->metadata->hasField($name)) { |
651
|
|
|
return method_exists($this->metadata, 'isNullable') && $this->metadata->isNullable($name); |
|
|
|
|
652
|
6 |
|
} else if ($this->metadata->hasAssociation($name) && method_exists($this->metadata, 'getAssociationMapping')) { |
653
|
|
|
$mapping = $this->metadata->getAssociationMapping($name); |
|
|
|
|
654
|
|
|
|
655
|
|
|
return false !== $mapping && isset($mapping['nullable']) && $mapping['nullable']; |
656
|
|
|
} |
657
|
|
|
|
658
|
6 |
|
return false; |
659
|
|
|
} |
660
|
|
|
|
661
|
|
|
/** |
662
|
|
|
* Applies the naming strategy if there is one set |
663
|
|
|
* |
664
|
|
|
* @param string $field |
665
|
|
|
* |
666
|
|
|
* @return string |
667
|
|
|
*/ |
668
|
55 |
|
protected function computeHydrateFieldName($field) |
669
|
|
|
{ |
670
|
55 |
|
if ($this->hasNamingStrategy()) { |
671
|
2 |
|
$field = $this->getNamingStrategy()->hydrate($field); |
672
|
|
|
} |
673
|
55 |
|
return $field; |
674
|
|
|
} |
675
|
|
|
|
676
|
|
|
/** |
677
|
|
|
* Applies the naming strategy if there is one set |
678
|
|
|
* |
679
|
|
|
* @param string $field |
680
|
|
|
* |
681
|
|
|
* @return string |
682
|
|
|
*/ |
683
|
17 |
|
protected function computeExtractFieldName($field) |
684
|
|
|
{ |
685
|
17 |
|
if ($this->hasNamingStrategy()) { |
686
|
2 |
|
$field = $this->getNamingStrategy()->extract($field); |
687
|
|
|
} |
688
|
17 |
|
return $field; |
689
|
|
|
} |
690
|
|
|
} |
691
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.