1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Bankiru\Api\Doctrine\Mapping; |
4
|
|
|
|
5
|
|
|
use Bankiru\Api\Doctrine\EntityRepository; |
6
|
|
|
use Bankiru\Api\Doctrine\Exception\MappingException; |
7
|
|
|
use Bankiru\Api\Doctrine\Rpc\Method\MethodProviderInterface; |
8
|
|
|
use Doctrine\Common\Persistence\Mapping\ReflectionService; |
9
|
|
|
use Doctrine\Instantiator\Instantiator; |
10
|
|
|
use Doctrine\Instantiator\InstantiatorInterface; |
11
|
|
|
|
12
|
|
|
class EntityMetadata implements ApiMetadata |
13
|
|
|
{ |
14
|
|
|
/** |
15
|
|
|
* The ReflectionProperty instances of the mapped class. |
16
|
|
|
* |
17
|
|
|
* @var \ReflectionProperty[] |
18
|
|
|
*/ |
19
|
|
|
public $reflFields = []; |
20
|
|
|
/** @var string */ |
21
|
|
|
public $name; |
22
|
|
|
/** @var string */ |
23
|
|
|
public $namespace; |
24
|
|
|
/** @var string */ |
25
|
|
|
public $rootEntityName; |
26
|
|
|
/** @var string[] */ |
27
|
|
|
public $identifier = []; |
28
|
|
|
/** @var array */ |
29
|
|
|
public $fields = []; |
30
|
|
|
/** @var array */ |
31
|
|
|
public $associations = []; |
32
|
|
|
/** @var string */ |
33
|
|
|
public $repositoryClass = EntityRepository::class; |
34
|
|
|
/** @var \ReflectionClass */ |
35
|
|
|
public $reflClass; |
36
|
|
|
/** @var MethodProviderInterface */ |
37
|
|
|
public $methodProvider; |
38
|
|
|
/** @var string */ |
39
|
|
|
public $clientName; |
40
|
|
|
/** @var string */ |
41
|
|
|
public $apiFactory; |
42
|
|
|
/** @var string[] */ |
43
|
|
|
public $apiFieldNames = []; |
44
|
|
|
/** @var string[] */ |
45
|
|
|
public $fieldNames = []; |
46
|
|
|
/** @var bool */ |
47
|
|
|
public $isMappedSuperclass = false; |
48
|
|
|
/** @var bool */ |
49
|
|
|
public $containsForeignIdentifier; |
50
|
|
|
/** @var bool */ |
51
|
|
|
public $isIdentifierComposite = false; |
52
|
|
|
/** @var int */ |
53
|
|
|
public $generatorType = self::GENERATOR_TYPE_NATURAL; |
54
|
|
|
/** @var string[] */ |
55
|
|
|
public $discriminatorField; |
56
|
|
|
/** @var string[] */ |
57
|
|
|
public $discriminatorMap; |
58
|
|
|
/** @var string */ |
59
|
|
|
public $discriminatorValue; |
60
|
|
|
/** @var string[] */ |
61
|
|
|
public $subclasses = []; |
62
|
|
|
/** @var InstantiatorInterface */ |
63
|
|
|
private $instantiator; |
64
|
|
|
/** @var int */ |
65
|
|
|
private $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT; |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Initializes a new ClassMetadata instance that will hold the object-relational mapping |
69
|
|
|
* metadata of the class with the given name. |
70
|
|
|
* |
71
|
|
|
* @param string $entityName The name of the entity class the new instance is used for. |
72
|
|
|
*/ |
73
|
28 |
|
public function __construct($entityName) |
74
|
|
|
{ |
75
|
28 |
|
$this->name = $entityName; |
76
|
28 |
|
$this->rootEntityName = $entityName; |
77
|
28 |
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @return boolean |
81
|
|
|
*/ |
82
|
|
|
public function containsForeignIdentifier() |
83
|
|
|
{ |
84
|
|
|
return $this->containsForeignIdentifier; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** {@inheritdoc} */ |
88
|
11 |
|
public function getReflectionProperties() |
89
|
|
|
{ |
90
|
11 |
|
return $this->reflFields; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* {@inheritdoc} |
95
|
|
|
*/ |
96
|
25 |
|
public function getReflectionProperty($name) |
97
|
|
|
{ |
98
|
25 |
|
if (!array_key_exists($name, $this->reflFields)) { |
99
|
|
|
throw MappingException::noSuchProperty($name, $this->getName()); |
100
|
|
|
} |
101
|
|
|
|
102
|
25 |
|
return $this->reflFields[$name]; |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** {@inheritdoc} */ |
106
|
28 |
|
public function getName() |
107
|
|
|
{ |
108
|
28 |
|
return $this->name; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** {@inheritdoc} */ |
112
|
27 |
|
public function getMethodContainer() |
113
|
|
|
{ |
114
|
27 |
|
return $this->methodProvider; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** {@inheritdoc} */ |
118
|
18 |
|
public function getRepositoryClass() |
119
|
|
|
{ |
120
|
18 |
|
return $this->repositoryClass; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** {@inheritdoc} */ |
124
|
2 |
|
public function getIdentifier() |
125
|
|
|
{ |
126
|
2 |
|
return $this->identifier; |
127
|
|
|
} |
128
|
|
|
|
129
|
14 |
|
public function setIdentifier($identifier) |
130
|
|
|
{ |
131
|
14 |
|
$this->identifier = $identifier; |
132
|
14 |
|
$this->isIdentifierComposite = (count($this->identifier) > 1); |
133
|
14 |
|
} |
134
|
|
|
|
135
|
|
|
/** {@inheritdoc} */ |
136
|
18 |
|
public function getReflectionClass() |
137
|
|
|
{ |
138
|
18 |
|
if (null === $this->reflClass) { |
139
|
|
|
$this->reflClass = new \ReflectionClass($this->getName()); |
140
|
|
|
} |
141
|
|
|
|
142
|
18 |
|
return $this->reflClass; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** {@inheritdoc} */ |
146
|
7 |
|
public function isIdentifier($fieldName) |
147
|
|
|
{ |
148
|
7 |
|
return in_array($fieldName, $this->identifier, true); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** {@inheritdoc} */ |
152
|
2 |
|
public function hasField($fieldName) |
153
|
|
|
{ |
154
|
2 |
|
return in_array($fieldName, $this->getFieldNames(), true); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** {@inheritdoc} */ |
158
|
18 |
|
public function getFieldNames() |
159
|
|
|
{ |
160
|
18 |
|
return array_keys($this->fields); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** {@inheritdoc} */ |
164
|
26 |
|
public function hasAssociation($fieldName) |
165
|
|
|
{ |
166
|
26 |
|
return in_array($fieldName, $this->getAssociationNames(), true); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** {@inheritdoc} */ |
170
|
26 |
|
public function getAssociationNames() |
171
|
|
|
{ |
172
|
26 |
|
return array_keys($this->associations); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** {@inheritdoc} */ |
176
|
12 |
|
public function isSingleValuedAssociation($fieldName) |
177
|
|
|
{ |
178
|
12 |
|
return $this->hasAssociation($fieldName) && $this->associations[$fieldName]['type'] & self::TO_ONE; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** {@inheritdoc} */ |
182
|
16 |
|
public function isCollectionValuedAssociation($fieldName) |
183
|
|
|
{ |
184
|
16 |
|
return $this->hasAssociation($fieldName) && $this->associations[$fieldName]['type'] & self::TO_MANY; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
/** {@inheritdoc} */ |
188
|
21 |
|
public function getIdentifierFieldNames() |
189
|
|
|
{ |
190
|
21 |
|
return $this->identifier; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** {@inheritdoc} */ |
194
|
26 |
|
public function getTypeOfField($fieldName) |
195
|
|
|
{ |
196
|
26 |
|
return $this->fields[$fieldName]['type']; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
/** {@inheritdoc} */ |
200
|
12 |
|
public function getAssociationTargetClass($assocName) |
201
|
|
|
{ |
202
|
12 |
|
return $this->associations[$assocName]['target']; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
/** {@inheritdoc} */ |
206
|
|
|
public function isAssociationInverseSide($assocName) |
207
|
|
|
{ |
208
|
|
|
$assoc = $this->associations[$assocName]; |
209
|
|
|
|
210
|
|
|
return array_key_exists('mappedBy', $assoc); |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** {@inheritdoc} */ |
214
|
|
|
public function getAssociationMappedByTargetField($assocName) |
215
|
|
|
{ |
216
|
|
|
return $this->associations[$assocName]['mappedBy']; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** {@inheritdoc} */ |
220
|
22 |
|
public function getIdentifierValues($object) |
221
|
|
|
{ |
222
|
22 |
|
if ($this->isIdentifierComposite) { |
223
|
1 |
|
$id = []; |
224
|
1 |
|
foreach ($this->identifier as $idField) { |
225
|
1 |
|
$value = $this->reflFields[$idField]->getValue($object); |
226
|
1 |
|
if ($value !== null) { |
227
|
1 |
|
$id[$idField] = $value; |
228
|
1 |
|
} |
229
|
1 |
|
} |
230
|
|
|
|
231
|
1 |
|
return $id; |
232
|
|
|
} |
233
|
21 |
|
$id = $this->identifier[0]; |
234
|
21 |
|
$value = $this->reflFields[$id]->getValue($object); |
235
|
21 |
|
if (null === $value) { |
236
|
|
|
return []; |
237
|
|
|
} |
238
|
|
|
|
239
|
21 |
|
return [$id => $value]; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** {@inheritdoc} */ |
243
|
28 |
|
public function wakeupReflection(ReflectionService $reflService) |
244
|
|
|
{ |
245
|
|
|
// Restore ReflectionClass and properties |
246
|
28 |
|
$this->reflClass = $reflService->getClass($this->name); |
247
|
28 |
|
$this->instantiator = $this->instantiator ?: new Instantiator(); |
248
|
|
|
|
249
|
28 |
View Code Duplication |
foreach ($this->fields as $field => $mapping) { |
|
|
|
|
250
|
28 |
|
$class = array_key_exists('declared', $mapping) ? $mapping['declared'] : $this->name; |
251
|
28 |
|
$this->reflFields[$field] = $reflService->getAccessibleProperty($class, $field); |
252
|
28 |
|
} |
253
|
|
|
|
254
|
28 |
View Code Duplication |
foreach ($this->associations as $field => $mapping) { |
|
|
|
|
255
|
18 |
|
$class = array_key_exists('declared', $mapping) ? $mapping['declared'] : $this->name; |
256
|
18 |
|
$this->reflFields[$field] = $reflService->getAccessibleProperty($class, $field); |
257
|
28 |
|
} |
258
|
28 |
|
} |
259
|
|
|
|
260
|
|
|
/** {@inheritdoc} */ |
261
|
28 |
|
public function initializeReflection(ReflectionService $reflService) |
262
|
|
|
{ |
263
|
28 |
|
$this->reflClass = $reflService->getClass($this->name); |
264
|
28 |
|
$this->namespace = $reflService->getClassNamespace($this->name); |
265
|
28 |
|
if ($this->reflClass) { |
266
|
28 |
|
$this->name = $this->rootEntityName = $this->reflClass->getName(); |
|
|
|
|
267
|
28 |
|
} |
268
|
28 |
|
} |
269
|
|
|
|
270
|
|
|
/** {@inheritdoc} */ |
271
|
26 |
|
public function getApiFactory() |
272
|
|
|
{ |
273
|
26 |
|
if (null === $this->apiFactory) { |
274
|
|
|
throw MappingException::noApiSpecified($this->getName()); |
275
|
|
|
} |
276
|
|
|
|
277
|
26 |
|
return $this->apiFactory; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** {@inheritdoc} */ |
281
|
27 |
|
public function getClientName() |
282
|
|
|
{ |
283
|
27 |
|
if (null === $this->clientName) { |
284
|
|
|
throw MappingException::noClientSpecified($this->getName()); |
285
|
|
|
} |
286
|
|
|
|
287
|
27 |
|
return $this->clientName; |
288
|
|
|
} |
289
|
|
|
|
290
|
28 |
|
public function mapField(array $mapping) |
291
|
|
|
{ |
292
|
28 |
|
$this->validateAndCompleteFieldMapping($mapping); |
293
|
28 |
|
$this->assertFieldNotMapped($mapping['field']); |
294
|
28 |
|
$this->fields[$mapping['field']] = $mapping; |
295
|
28 |
|
} |
296
|
|
|
|
297
|
|
|
/** {@inheritdoc} */ |
298
|
26 |
|
public function getFieldMapping($fieldName) |
299
|
|
|
{ |
300
|
26 |
|
if (!isset($this->fields[$fieldName])) { |
301
|
|
|
throw MappingException::unknownField($fieldName, $this->getName()); |
302
|
|
|
} |
303
|
|
|
|
304
|
26 |
|
return $this->fields[$fieldName]; |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
/** {@inheritdoc} */ |
308
|
26 |
|
public function getFieldOptions($fieldName) |
309
|
|
|
{ |
310
|
26 |
|
return $this->getFieldMapping($fieldName)['options']; |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** {@inheritdoc} */ |
314
|
17 |
|
public function getAssociationMapping($fieldName) |
315
|
|
|
{ |
316
|
17 |
|
if (!isset($this->associations[$fieldName])) { |
317
|
|
|
throw MappingException::unknownAssociation($fieldName, $this->getName()); |
318
|
|
|
} |
319
|
|
|
|
320
|
17 |
|
return $this->associations[$fieldName]; |
321
|
|
|
} |
322
|
|
|
|
323
|
2 |
|
public function setCustomRepositoryClass($customRepositoryClassName) |
324
|
|
|
{ |
325
|
2 |
|
$this->repositoryClass = $customRepositoryClassName; |
326
|
2 |
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* @internal |
330
|
|
|
* |
331
|
|
|
* @param array $mapping |
332
|
|
|
* |
333
|
|
|
* @return void |
334
|
|
|
*/ |
335
|
14 |
|
public function addInheritedFieldMapping(array $mapping) |
336
|
|
|
{ |
337
|
14 |
|
$this->fields[$mapping['field']] = $mapping; |
338
|
14 |
|
$this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
339
|
14 |
|
$this->fieldNames[$mapping['api_field']] = $mapping['field']; |
340
|
14 |
|
} |
341
|
|
|
|
342
|
|
|
/** {@inheritdoc} */ |
343
|
7 |
|
public function getFieldName($apiFieldName) |
344
|
|
|
{ |
345
|
7 |
|
return $this->fieldNames[$apiFieldName]; |
346
|
|
|
} |
347
|
|
|
|
348
|
|
|
/** {@inheritdoc} */ |
349
|
26 |
|
public function getApiFieldName($fieldName) |
350
|
|
|
{ |
351
|
26 |
|
return $this->apiFieldNames[$fieldName]; |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
public function hasApiField($apiFieldName) |
355
|
|
|
{ |
356
|
|
|
return array_key_exists($apiFieldName, $this->fieldNames); |
357
|
|
|
} |
358
|
|
|
|
359
|
17 |
|
public function mapOneToMany(array $mapping) |
360
|
|
|
{ |
361
|
17 |
|
$mapping = $this->validateAndCompleteOneToManyMapping($mapping); |
362
|
|
|
|
363
|
17 |
|
$this->storeMapping($mapping); |
364
|
17 |
|
} |
365
|
|
|
|
366
|
17 |
|
public function mapManyToOne(array $mapping) |
367
|
|
|
{ |
368
|
17 |
|
$mapping = $this->validateAndCompleteOneToOneMapping($mapping); |
369
|
|
|
|
370
|
17 |
|
$this->storeMapping($mapping); |
371
|
17 |
|
} |
372
|
|
|
|
373
|
|
|
public function mapOneToOne(array $mapping) |
374
|
|
|
{ |
375
|
|
|
$mapping = $this->validateAndCompleteOneToOneMapping($mapping); |
376
|
|
|
|
377
|
|
|
$this->storeMapping($mapping); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
/** {@inheritdoc} */ |
381
|
17 |
|
public function newInstance() |
382
|
|
|
{ |
383
|
17 |
|
return $this->instantiator->instantiate($this->name); |
384
|
|
|
} |
385
|
|
|
|
386
|
8 |
|
public function isIdentifierComposite() |
387
|
|
|
{ |
388
|
8 |
|
return $this->isIdentifierComposite; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
/** {@inheritdoc} */ |
392
|
28 |
|
public function getRootEntityName() |
393
|
|
|
{ |
394
|
28 |
|
return $this->rootEntityName; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
/** |
398
|
|
|
* Populates the entity identifier of an entity. |
399
|
|
|
* |
400
|
|
|
* @param object $entity |
401
|
|
|
* @param array $id |
402
|
|
|
* |
403
|
|
|
* @return void |
404
|
|
|
*/ |
405
|
|
|
public function assignIdentifier($entity, array $id) |
406
|
|
|
{ |
407
|
|
|
foreach ($id as $idField => $idValue) { |
408
|
|
|
$this->reflFields[$idField]->setValue($entity, $idValue); |
409
|
|
|
} |
410
|
|
|
} |
411
|
|
|
|
412
|
10 |
|
public function addInheritedAssociationMapping(array $mapping) |
413
|
|
|
{ |
414
|
10 |
|
$this->associations[$mapping['field']] = $mapping; |
415
|
10 |
|
$this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
416
|
10 |
|
$this->fieldNames[$mapping['api_field']] = $mapping['field']; |
417
|
10 |
|
} |
418
|
|
|
|
419
|
|
|
/** {@inheritdoc} */ |
420
|
7 |
|
public function getSubclasses() |
421
|
|
|
{ |
422
|
7 |
|
return $this->subclasses; |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** {@inheritdoc} */ |
426
|
7 |
|
public function getAssociationMappings() |
427
|
|
|
{ |
428
|
7 |
|
return $this->associations; |
429
|
|
|
} |
430
|
|
|
|
431
|
3 |
|
public function isReadOnly() |
432
|
|
|
{ |
433
|
3 |
|
return false; |
434
|
|
|
} |
435
|
|
|
|
436
|
|
|
/** |
437
|
|
|
* Sets the change tracking policy used by this class. |
438
|
|
|
* |
439
|
|
|
* @param integer $policy |
440
|
|
|
* |
441
|
|
|
* @return void |
442
|
|
|
*/ |
443
|
|
|
public function setChangeTrackingPolicy($policy) |
444
|
|
|
{ |
445
|
|
|
$this->changeTrackingPolicy = $policy; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** |
449
|
|
|
* Whether the change tracking policy of this class is "deferred explicit". |
450
|
|
|
* |
451
|
|
|
* @return boolean |
452
|
|
|
*/ |
453
|
|
|
public function isChangeTrackingDeferredExplicit() |
454
|
|
|
{ |
455
|
|
|
return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT; |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* Whether the change tracking policy of this class is "deferred implicit". |
460
|
|
|
* |
461
|
|
|
* @return boolean |
462
|
|
|
*/ |
463
|
3 |
|
public function isChangeTrackingDeferredImplicit() |
464
|
|
|
{ |
465
|
3 |
|
return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT; |
466
|
|
|
} |
467
|
|
|
|
468
|
|
|
/** |
469
|
|
|
* Whether the change tracking policy of this class is "notify". |
470
|
|
|
* |
471
|
|
|
* @return boolean |
472
|
|
|
*/ |
473
|
|
|
public function isChangeTrackingNotify() |
474
|
|
|
{ |
475
|
|
|
return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY; |
476
|
|
|
} |
477
|
|
|
|
478
|
28 |
|
public function mapIdentifier(array $mapping) |
479
|
|
|
{ |
480
|
28 |
|
$this->setIdGeneratorType($mapping['generator']['strategy']); |
481
|
|
|
|
482
|
28 |
|
$this->mapField($mapping); |
483
|
28 |
|
} |
484
|
|
|
|
485
|
|
|
/** |
486
|
|
|
* Sets the type of Id generator to use for the mapped class. |
487
|
|
|
* |
488
|
|
|
* @param int $generatorType |
489
|
|
|
* |
490
|
|
|
* @return void |
491
|
|
|
*/ |
492
|
28 |
|
public function setIdGeneratorType($generatorType) |
493
|
|
|
{ |
494
|
28 |
|
$this->generatorType = $generatorType; |
495
|
28 |
|
} |
496
|
|
|
|
497
|
7 |
|
public function isIdentifierNatural() |
498
|
|
|
{ |
499
|
7 |
|
return $this->generatorType === self::GENERATOR_TYPE_NATURAL; |
500
|
|
|
} |
501
|
|
|
|
502
|
7 |
|
public function isIdentifierRemote() |
503
|
|
|
{ |
504
|
7 |
|
return $this->generatorType === self::GENERATOR_TYPE_REMOTE; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Populates the entity identifier of an entity. |
509
|
|
|
* |
510
|
|
|
* @param object $entity |
511
|
|
|
* @param array $id |
512
|
|
|
* |
513
|
|
|
* @return void |
514
|
|
|
* |
515
|
|
|
* @todo Rename to assignIdentifier() |
516
|
|
|
*/ |
517
|
|
|
public function setIdentifierValues($entity, array $id) |
518
|
|
|
{ |
519
|
|
|
foreach ($id as $idField => $idValue) { |
520
|
|
|
$this->reflFields[$idField]->setValue($entity, $idValue); |
521
|
|
|
} |
522
|
|
|
} |
523
|
|
|
|
524
|
28 |
|
public function isRootEntity() |
525
|
|
|
{ |
526
|
28 |
|
return $this->getRootEntityName() === $this->getName(); |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
/** |
530
|
|
|
* Adds one entry of the discriminator map with a new class and corresponding name. |
531
|
|
|
* |
532
|
|
|
* @param string $name |
533
|
|
|
* @param string $className |
534
|
|
|
* |
535
|
|
|
* @return void |
536
|
|
|
* |
537
|
|
|
* @throws MappingException |
538
|
|
|
*/ |
539
|
28 |
|
public function addDiscriminatorMapClass($name, $className) |
540
|
|
|
{ |
541
|
28 |
|
$className = $this->fullyQualifiedClassName($className); |
542
|
28 |
|
$className = ltrim($className, '\\'); |
543
|
|
|
|
544
|
28 |
|
if (!(class_exists($className) || interface_exists($className))) { |
545
|
|
|
throw MappingException::invalidClassInDiscriminatorMap($className, $this->name); |
546
|
|
|
} |
547
|
|
|
|
548
|
28 |
|
$refl = new \ReflectionClass($className); |
549
|
28 |
|
if ($refl->isAbstract()) { |
550
|
4 |
|
return; |
551
|
|
|
} |
552
|
|
|
|
553
|
28 |
|
$this->discriminatorMap[$name] = $className; |
554
|
|
|
|
555
|
28 |
|
if ($this->name === $className) { |
556
|
28 |
|
$this->discriminatorValue = $name; |
557
|
|
|
|
558
|
28 |
|
return; |
559
|
|
|
} |
560
|
|
|
|
561
|
20 |
|
if (is_subclass_of($className, $this->name) && !in_array($className, $this->subclasses)) { |
|
|
|
|
562
|
20 |
|
$this->subclasses[] = $className; |
563
|
20 |
|
} |
564
|
20 |
|
} |
565
|
|
|
|
566
|
|
|
/** |
567
|
|
|
* @param string|null $className |
568
|
|
|
* |
569
|
|
|
* @return string|null null if the input value is null |
570
|
|
|
*/ |
571
|
28 |
|
public function fullyQualifiedClassName($className) |
572
|
|
|
{ |
573
|
28 |
|
if (empty($className)) { |
574
|
|
|
return $className; |
575
|
|
|
} |
576
|
28 |
|
if ($className !== null && strpos($className, '\\') === false && $this->namespace) { |
577
|
|
|
return $this->namespace . '\\' . $className; |
578
|
|
|
} |
579
|
|
|
|
580
|
28 |
|
return $className; |
581
|
|
|
} |
582
|
|
|
|
583
|
|
|
/** {@inheritdoc} */ |
584
|
26 |
|
public function getDiscriminatorField() |
585
|
|
|
{ |
586
|
26 |
|
return $this->discriminatorField; |
|
|
|
|
587
|
|
|
} |
588
|
|
|
|
589
|
|
|
/** |
590
|
|
|
* Sets the discriminator column definition. |
591
|
|
|
* |
592
|
|
|
* @param array $columnDef |
593
|
|
|
* |
594
|
|
|
* @return void |
595
|
|
|
* |
596
|
|
|
* @throws MappingException |
597
|
|
|
* |
598
|
|
|
* @see getDiscriminatorColumn() |
599
|
|
|
*/ |
600
|
14 |
|
public function setDiscriminatorField(array $columnDef = null) |
601
|
|
|
{ |
602
|
14 |
|
if ($columnDef !== null) { |
603
|
4 |
|
if (!isset($columnDef['name'])) { |
604
|
|
|
throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name); |
605
|
|
|
} |
606
|
4 |
|
if (isset($this->fieldNames[$columnDef['name']])) { |
607
|
|
|
throw MappingException::duplicateColumnName($this->name, $columnDef['name']); |
608
|
|
|
} |
609
|
4 |
|
if (!isset($columnDef['fieldName'])) { |
610
|
4 |
|
$columnDef['fieldName'] = $columnDef['name']; |
611
|
4 |
|
} |
612
|
4 |
|
if (!isset($columnDef['type'])) { |
613
|
|
|
$columnDef['type'] = 'string'; |
614
|
|
|
} |
615
|
4 |
|
if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) { |
616
|
|
|
throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']); |
617
|
|
|
} |
618
|
4 |
|
$this->discriminatorField = $columnDef; |
|
|
|
|
619
|
4 |
|
} |
620
|
14 |
|
} |
621
|
|
|
|
622
|
|
|
/** {@inheritdoc} */ |
623
|
18 |
|
public function getDiscriminatorMap() |
624
|
|
|
{ |
625
|
18 |
|
return $this->discriminatorMap; |
|
|
|
|
626
|
|
|
} |
627
|
|
|
|
628
|
|
|
/** |
629
|
|
|
* Sets the discriminator values used by this class. |
630
|
|
|
* |
631
|
|
|
* @param array $map |
632
|
|
|
* |
633
|
|
|
* @return void |
634
|
|
|
*/ |
635
|
28 |
|
public function setDiscriminatorMap(array $map) |
636
|
|
|
{ |
637
|
28 |
|
foreach ($map as $value => $className) { |
638
|
28 |
|
$this->addDiscriminatorMapClass($value, $className); |
639
|
28 |
|
} |
640
|
28 |
|
} |
641
|
|
|
|
642
|
|
|
/** {@inheritdoc} */ |
643
|
19 |
|
public function getDiscriminatorValue() |
644
|
|
|
{ |
645
|
19 |
|
return $this->discriminatorValue; |
646
|
|
|
} |
647
|
|
|
|
648
|
1 |
|
public function mapManyToMany($mapping) |
649
|
|
|
{ |
650
|
1 |
|
$mapping = $this->validateAndCompleteManyToManyMapping($mapping); |
651
|
|
|
|
652
|
1 |
|
$this->storeMapping($mapping); |
653
|
1 |
|
} |
654
|
|
|
|
655
|
|
|
/** |
656
|
|
|
* Validates & completes the basic mapping information that is common to all |
657
|
|
|
* association mappings (one-to-one, many-ot-one, one-to-many, many-to-many). |
658
|
|
|
* |
659
|
|
|
* @param array $mapping The mapping. |
660
|
|
|
* |
661
|
|
|
* @return array The updated mapping. |
662
|
|
|
* |
663
|
|
|
* @throws MappingException If something is wrong with the mapping. |
664
|
|
|
*/ |
665
|
18 |
|
protected function validateAndCompleteAssociationMapping(array $mapping) |
666
|
|
|
{ |
667
|
18 |
|
if (!array_key_exists('api_field', $mapping)) { |
668
|
17 |
|
$mapping['api_field'] = $mapping['field']; |
669
|
17 |
|
} |
670
|
|
|
|
671
|
18 |
|
if (!isset($mapping['mappedBy'])) { |
672
|
18 |
|
$mapping['mappedBy'] = null; |
673
|
18 |
|
} |
674
|
|
|
|
675
|
18 |
|
if (!isset($mapping['inversedBy'])) { |
676
|
18 |
|
$mapping['inversedBy'] = null; |
677
|
18 |
|
} |
678
|
|
|
|
679
|
18 |
|
if (!isset($mapping['orderBy'])) { |
680
|
18 |
|
$mapping['orderBy'] = []; |
681
|
18 |
|
} |
682
|
|
|
|
683
|
18 |
|
$mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy |
684
|
|
|
|
685
|
|
|
// unset optional indexBy attribute if its empty |
686
|
18 |
|
if (!isset($mapping['indexBy']) || !$mapping['indexBy']) { |
687
|
18 |
|
unset($mapping['indexBy']); |
688
|
18 |
|
} |
689
|
|
|
|
690
|
|
|
// If targetEntity is unqualified, assume it is in the same namespace as |
691
|
|
|
// the sourceEntity. |
692
|
18 |
|
$mapping['source'] = $this->name; |
693
|
18 |
|
if (isset($mapping['target'])) { |
694
|
18 |
|
$mapping['target'] = ltrim($mapping['target'], '\\'); |
695
|
18 |
|
} |
696
|
|
|
|
697
|
18 |
|
if (($mapping['type'] & self::MANY_TO_ONE) > 0 && |
698
|
18 |
|
isset($mapping['orphanRemoval']) && |
699
|
|
|
$mapping['orphanRemoval'] == true |
700
|
18 |
|
) { |
701
|
|
|
throw new MappingException( |
702
|
|
|
sprintf('Illegal orphanRemoval %s for %s', $mapping['field'], $this->name) |
703
|
|
|
); |
704
|
|
|
} |
705
|
|
|
|
706
|
|
|
// Complete id mapping |
707
|
18 |
|
if (isset($mapping['id']) && $mapping['id'] === true) { |
708
|
|
View Code Duplication |
if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) { |
|
|
|
|
709
|
|
|
throw new MappingException( |
710
|
|
|
sprintf('Illegal orphanRemoval on identifier association %s for %s', $mapping['field'], $this->name) |
711
|
|
|
); |
712
|
|
|
} |
713
|
|
|
|
714
|
|
|
if (!in_array($mapping['field'], $this->identifier, true)) { |
715
|
|
|
$this->identifier[] = $mapping['field']; |
716
|
|
|
$this->containsForeignIdentifier = true; |
717
|
|
|
} |
718
|
|
|
|
719
|
|
|
// Check for composite key |
720
|
|
|
if (!$this->isIdentifierComposite && count($this->identifier) > 1) { |
721
|
|
|
$this->isIdentifierComposite = true; |
722
|
|
|
} |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
// Mandatory and optional attributes for either side |
726
|
18 |
|
if (null !== $mapping['mappedBy']) { |
727
|
17 |
|
$mapping['isOwningSide'] = false; |
728
|
17 |
|
} |
729
|
|
|
|
730
|
18 |
View Code Duplication |
if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) { |
|
|
|
|
731
|
|
|
throw new MappingException( |
732
|
|
|
sprintf('Illegal toMany identifier association %s for %s', $mapping['field'], $this->name) |
733
|
|
|
); |
734
|
|
|
} |
735
|
|
|
|
736
|
|
|
// Fetch mode. Default fetch mode to LAZY, if not set. |
737
|
18 |
|
if (!isset($mapping['fetch'])) { |
738
|
18 |
|
$mapping['fetch'] = self::FETCH_LAZY; |
739
|
18 |
|
} |
740
|
|
|
|
741
|
|
|
// Cascades |
742
|
18 |
|
$cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : []; |
743
|
18 |
|
$allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach']; |
744
|
18 |
|
if (in_array('all', $cascades, true)) { |
745
|
|
|
$cascades = $allCascades; |
746
|
18 |
|
} elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) { |
747
|
|
|
throw new MappingException('Invalid cascades: ' . implode(', ', $cascades)); |
748
|
|
|
} |
749
|
18 |
|
$mapping['cascade'] = $cascades; |
750
|
18 |
|
$mapping['isCascadeRemove'] = in_array('remove', $cascades, true); |
751
|
18 |
|
$mapping['isCascadePersist'] = in_array('persist', $cascades, true); |
752
|
18 |
|
$mapping['isCascadeRefresh'] = in_array('refresh', $cascades, true); |
753
|
18 |
|
$mapping['isCascadeMerge'] = in_array('merge', $cascades, true); |
754
|
18 |
|
$mapping['isCascadeDetach'] = in_array('detach', $cascades, true); |
755
|
|
|
|
756
|
18 |
|
return $mapping; |
757
|
|
|
} |
758
|
|
|
|
759
|
18 |
|
private function storeMapping(array $mapping) |
760
|
|
|
{ |
761
|
18 |
|
$this->assertFieldNotMapped($mapping['field']); |
762
|
|
|
|
763
|
18 |
|
$this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
764
|
18 |
|
$this->fieldNames[$mapping['api_field']] = $mapping['field']; |
765
|
18 |
|
$this->associations[$mapping['field']] = $mapping; |
766
|
18 |
|
} |
767
|
|
|
|
768
|
28 |
|
private function validateAndCompleteFieldMapping(array &$mapping) |
769
|
|
|
{ |
770
|
28 |
|
if (!array_key_exists('api_field', $mapping)) { |
771
|
27 |
|
$mapping['api_field'] = $mapping['field']; //todo: invent naming strategy |
772
|
27 |
|
} |
773
|
|
|
|
774
|
28 |
|
if (!array_key_exists('options', $mapping)) { |
775
|
|
|
$mapping['options'] = []; |
776
|
|
|
} |
777
|
|
|
|
778
|
28 |
|
$this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
779
|
28 |
|
$this->fieldNames[$mapping['api_field']] = $mapping['field']; |
780
|
|
|
|
781
|
|
|
// if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorField && $this->discriminatorField['name'] === $mapping['api_field'])) { |
|
|
|
|
782
|
|
|
// throw MappingException::duplicateColumnName($this->name, $mapping['columnName']); |
|
|
|
|
783
|
|
|
// } |
784
|
|
|
|
785
|
|
|
// Complete id mapping |
786
|
28 |
|
if (isset($mapping['id']) && $mapping['id'] === true) { |
787
|
28 |
|
if (!in_array($mapping['field'], $this->identifier, true)) { |
788
|
28 |
|
$this->identifier[] = $mapping['field']; |
789
|
28 |
|
} |
790
|
|
|
// Check for composite key |
791
|
28 |
|
if (!$this->isIdentifierComposite && count($this->identifier) > 1) { |
792
|
1 |
|
$this->isIdentifierComposite = true; |
793
|
1 |
|
} |
794
|
28 |
|
} |
795
|
28 |
|
} |
796
|
|
|
|
797
|
|
|
/** |
798
|
|
|
* @param string $fieldName |
799
|
|
|
* |
800
|
|
|
* @throws MappingException |
801
|
|
|
*/ |
802
|
28 |
|
private function assertFieldNotMapped($fieldName) |
803
|
|
|
{ |
804
|
28 |
|
if (array_key_exists($fieldName, $this->fields) || |
805
|
28 |
|
array_key_exists($fieldName, $this->associations) || |
806
|
28 |
|
array_key_exists($fieldName, $this->identifier) |
807
|
28 |
|
) { |
808
|
|
|
throw new MappingException('Field already mapped'); |
809
|
|
|
} |
810
|
28 |
|
} |
811
|
|
|
|
812
|
|
|
/** |
813
|
|
|
* @param array $mapping |
814
|
|
|
* |
815
|
|
|
* @return array |
816
|
|
|
* @throws MappingException |
817
|
|
|
* @throws \InvalidArgumentException |
818
|
|
|
*/ |
819
|
17 |
|
private function validateAndCompleteOneToManyMapping(array $mapping) |
820
|
|
|
{ |
821
|
17 |
|
$mapping = $this->validateAndCompleteAssociationMapping($mapping); |
822
|
|
|
|
823
|
|
|
// OneToMany-side MUST be inverse (must have mappedBy) |
824
|
17 |
|
if (!isset($mapping['mappedBy'])) { |
825
|
|
|
throw new MappingException( |
826
|
|
|
sprintf('Many to many requires mapped by: %s', $mapping['field']) |
827
|
|
|
); |
828
|
|
|
} |
829
|
17 |
|
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']; |
830
|
17 |
|
$mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove']; |
831
|
17 |
|
$this->assertMappingOrderBy($mapping); |
832
|
|
|
|
833
|
17 |
|
return $mapping; |
834
|
|
|
} |
835
|
|
|
|
836
|
|
|
/** |
837
|
|
|
* @param array $mapping |
838
|
|
|
* |
839
|
|
|
* @return array |
840
|
|
|
* @throws MappingException |
841
|
|
|
* @throws \InvalidArgumentException |
842
|
|
|
*/ |
843
|
1 |
|
private function validateAndCompleteManyToManyMapping(array $mapping) |
844
|
|
|
{ |
845
|
1 |
|
$mapping = $this->validateAndCompleteAssociationMapping($mapping); |
846
|
|
|
|
847
|
1 |
|
$this->assertMappingOrderBy($mapping); |
848
|
|
|
|
849
|
1 |
|
return $mapping; |
850
|
|
|
} |
851
|
|
|
|
852
|
|
|
/** |
853
|
|
|
* @param array $mapping |
854
|
|
|
* |
855
|
|
|
* @throws \InvalidArgumentException |
856
|
|
|
*/ |
857
|
18 |
|
private function assertMappingOrderBy(array $mapping) |
858
|
|
|
{ |
859
|
18 |
|
if (array_key_exists('orderBy', $mapping) && !is_array($mapping['orderBy'])) { |
860
|
|
|
throw new \InvalidArgumentException( |
861
|
|
|
"'orderBy' is expected to be an array, not " . gettype($mapping['orderBy']) |
862
|
|
|
); |
863
|
|
|
} |
864
|
18 |
|
} |
865
|
|
|
|
866
|
|
|
/** |
867
|
|
|
* @param array $mapping |
868
|
|
|
* |
869
|
|
|
* @return array |
870
|
|
|
*/ |
871
|
17 |
|
private function validateAndCompleteOneToOneMapping(array $mapping) |
872
|
|
|
{ |
873
|
17 |
|
$mapping = $this->validateAndCompleteAssociationMapping($mapping); |
874
|
|
|
|
875
|
17 |
|
$mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']; |
876
|
17 |
|
$mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove']; |
877
|
17 |
|
if ($mapping['orphanRemoval']) { |
878
|
|
|
unset($mapping['unique']); |
879
|
|
|
} |
880
|
|
|
|
881
|
17 |
|
return $mapping; |
882
|
|
|
} |
883
|
|
|
} |
884
|
|
|
|
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.