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 $apiFieldNames = []; |
42
|
|
|
/** @var string[] */ |
43
|
|
|
public $fieldNames = []; |
44
|
|
|
/** @var bool */ |
45
|
|
|
public $isMappedSuperclass = false; |
46
|
|
|
/** @var bool */ |
47
|
|
|
public $containsForeignIdentifier; |
48
|
|
|
/** @var bool */ |
49
|
|
|
public $isIdentifierComposite = false; |
50
|
|
|
/** @var string */ |
51
|
|
|
public $searcher; |
52
|
|
|
/** @var string */ |
53
|
|
|
public $finder; |
54
|
|
|
/** @var InstantiatorInterface */ |
55
|
|
|
private $instantiator; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Initializes a new ClassMetadata instance that will hold the object-relational mapping |
59
|
|
|
* metadata of the class with the given name. |
60
|
|
|
* |
61
|
|
|
* @param string $entityName The name of the entity class the new instance is used for. |
62
|
|
|
*/ |
63
|
14 |
|
public function __construct($entityName) |
64
|
|
|
{ |
65
|
14 |
|
$this->name = $entityName; |
66
|
14 |
|
$this->rootEntityName = $entityName; |
67
|
14 |
|
} |
68
|
|
|
|
69
|
|
|
/** |
70
|
|
|
* @return boolean |
71
|
|
|
*/ |
72
|
|
|
public function containsForeignIdentifier() |
73
|
|
|
{ |
74
|
|
|
return $this->containsForeignIdentifier; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** {@inheritdoc} */ |
78
|
3 |
|
public function getReflectionProperties() |
79
|
|
|
{ |
80
|
3 |
|
return $this->reflFields; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* {@inheritdoc} |
85
|
|
|
*/ |
86
|
12 |
|
public function getReflectionProperty($name) |
87
|
|
|
{ |
88
|
12 |
|
if (!array_key_exists($name, $this->reflFields)) { |
89
|
|
|
throw MappingException::noSuchProperty($name, $this->getName()); |
90
|
|
|
} |
91
|
|
|
|
92
|
12 |
|
return $this->reflFields[$name]; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** {@inheritdoc} */ |
96
|
14 |
|
public function getName() |
97
|
|
|
{ |
98
|
14 |
|
return $this->name; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** {@inheritdoc} */ |
102
|
14 |
|
public function getMethodContainer() |
103
|
|
|
{ |
104
|
14 |
|
return $this->methodProvider; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** {@inheritdoc} */ |
108
|
13 |
|
public function getRepositoryClass() |
109
|
|
|
{ |
110
|
13 |
|
return $this->repositoryClass; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** {@inheritdoc} */ |
114
|
2 |
|
public function getIdentifier() |
115
|
|
|
{ |
116
|
2 |
|
return $this->identifier; |
117
|
|
|
} |
118
|
|
|
|
119
|
6 |
|
public function setIdentifier($identifier) |
120
|
|
|
{ |
121
|
6 |
|
$this->identifier = $identifier; |
122
|
6 |
|
$this->isIdentifierComposite = (count($this->identifier) > 1); |
123
|
6 |
|
} |
124
|
|
|
|
125
|
|
|
/** {@inheritdoc} */ |
126
|
12 |
|
public function getReflectionClass() |
127
|
|
|
{ |
128
|
12 |
|
if (null === $this->reflClass) { |
129
|
|
|
$this->reflClass = new \ReflectionClass($this->getName()); |
130
|
|
|
} |
131
|
|
|
|
132
|
12 |
|
return $this->reflClass; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** {@inheritdoc} */ |
136
|
|
|
public function isIdentifier($fieldName) |
137
|
|
|
{ |
138
|
|
|
return in_array($fieldName, $this->identifier, true); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** {@inheritdoc} */ |
142
|
2 |
|
public function hasField($fieldName) |
143
|
|
|
{ |
144
|
2 |
|
return in_array($fieldName, $this->getFieldNames(), true); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** {@inheritdoc} */ |
148
|
12 |
|
public function getFieldNames() |
149
|
|
|
{ |
150
|
12 |
|
return array_keys($this->fields); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** {@inheritdoc} */ |
154
|
13 |
|
public function hasAssociation($fieldName) |
155
|
|
|
{ |
156
|
13 |
|
return in_array($fieldName, $this->getAssociationNames(), true); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** {@inheritdoc} */ |
160
|
13 |
|
public function getAssociationNames() |
161
|
|
|
{ |
162
|
13 |
|
return array_keys($this->associations); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** {@inheritdoc} */ |
166
|
9 |
|
public function isSingleValuedAssociation($fieldName) |
167
|
|
|
{ |
168
|
9 |
|
return $this->associations[$fieldName]['type'] & self::TO_ONE; |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** {@inheritdoc} */ |
172
|
9 |
|
public function isCollectionValuedAssociation($fieldName) |
173
|
|
|
{ |
174
|
9 |
|
return $this->associations[$fieldName]['type'] & self::TO_MANY; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** {@inheritdoc} */ |
178
|
12 |
|
public function getIdentifierFieldNames() |
179
|
|
|
{ |
180
|
12 |
|
return $this->identifier; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** {@inheritdoc} */ |
184
|
12 |
|
public function getTypeOfField($fieldName) |
185
|
|
|
{ |
186
|
12 |
|
return $this->fields[$fieldName]['type']; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** {@inheritdoc} */ |
190
|
9 |
|
public function getAssociationTargetClass($assocName) |
191
|
|
|
{ |
192
|
9 |
|
return $this->associations[$assocName]['target']; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
/** {@inheritdoc} */ |
196
|
|
|
public function isAssociationInverseSide($assocName) |
197
|
|
|
{ |
198
|
|
|
$assoc = $this->associations[$assocName]; |
199
|
|
|
|
200
|
|
|
return array_key_exists('mappedBy', $assoc); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** {@inheritdoc} */ |
204
|
|
|
public function getAssociationMappedByTargetField($assocName) |
205
|
|
|
{ |
206
|
|
|
return $this->associations[$assocName]['mappedBy']; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
/** {@inheritdoc} */ |
210
|
12 |
|
public function getIdentifierValues($object) |
211
|
|
|
{ |
212
|
12 |
|
if ($this->isIdentifierComposite) { |
213
|
1 |
|
$id = []; |
214
|
1 |
|
foreach ($this->identifier as $idField) { |
215
|
1 |
|
$value = $this->reflFields[$idField]->getValue($object); |
216
|
1 |
|
if ($value !== null) { |
217
|
1 |
|
$id[$idField] = $value; |
218
|
1 |
|
} |
219
|
1 |
|
} |
220
|
|
|
|
221
|
1 |
|
return $id; |
222
|
|
|
} |
223
|
11 |
|
$id = $this->identifier[0]; |
224
|
11 |
|
$value = $this->reflFields[$id]->getValue($object); |
225
|
11 |
|
if (null === $value) { |
226
|
|
|
return []; |
227
|
|
|
} |
228
|
|
|
|
229
|
11 |
|
return [$id => $value]; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
/** {@inheritdoc} */ |
233
|
14 |
|
public function wakeupReflection(ReflectionService $reflService) |
234
|
|
|
{ |
235
|
|
|
// Restore ReflectionClass and properties |
236
|
14 |
|
$this->reflClass = $reflService->getClass($this->name); |
237
|
14 |
|
$this->instantiator = $this->instantiator ?: new Instantiator(); |
238
|
|
|
|
239
|
14 |
View Code Duplication |
foreach ($this->fields as $field => $mapping) { |
|
|
|
|
240
|
14 |
|
$class = array_key_exists('declared', $mapping) ? $mapping['declared'] : $this->name; |
241
|
14 |
|
$this->reflFields[$field] = $reflService->getAccessibleProperty($class, $field); |
242
|
14 |
|
} |
243
|
|
|
|
244
|
14 |
View Code Duplication |
foreach ($this->associations as $field => $mapping) { |
|
|
|
|
245
|
10 |
|
$class = array_key_exists('declared', $mapping) ? $mapping['declared'] : $this->name; |
246
|
10 |
|
$this->reflFields[$field] = $reflService->getAccessibleProperty($class, $field); |
247
|
14 |
|
} |
248
|
14 |
|
} |
249
|
|
|
|
250
|
|
|
/** {@inheritdoc} */ |
251
|
14 |
|
public function initializeReflection(ReflectionService $reflService) |
252
|
|
|
{ |
253
|
14 |
|
$this->reflClass = $reflService->getClass($this->name); |
254
|
14 |
|
$this->namespace = $reflService->getClassNamespace($this->name); |
255
|
14 |
|
if ($this->reflClass) { |
256
|
14 |
|
$this->name = $this->rootEntityName = $this->reflClass->getName(); |
|
|
|
|
257
|
14 |
|
} |
258
|
14 |
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* {@inheritdoc} |
262
|
|
|
* @throws MappingException |
263
|
|
|
*/ |
264
|
14 |
|
public function getClientName() |
265
|
|
|
{ |
266
|
14 |
|
if (null === $this->clientName) { |
267
|
|
|
throw MappingException::invalidClientName($this->getName()); |
268
|
|
|
} |
269
|
|
|
|
270
|
14 |
|
return $this->clientName; |
271
|
|
|
} |
272
|
|
|
|
273
|
14 |
|
public function mapField(array $mapping) |
274
|
|
|
{ |
275
|
14 |
|
$this->validateAndCompleteFieldMapping($mapping); |
276
|
14 |
|
$this->assertFieldNotMapped($mapping['field']); |
277
|
14 |
|
$this->fields[$mapping['field']] = $mapping; |
278
|
14 |
|
} |
279
|
|
|
|
280
|
14 |
|
private function validateAndCompleteFieldMapping(array &$mapping) |
281
|
|
|
{ |
282
|
14 |
|
if (!array_key_exists('api_field', $mapping)) { |
283
|
14 |
|
$mapping['api_field'] = $mapping['field']; //todo: invent naming strategy |
284
|
14 |
|
} |
285
|
|
|
|
286
|
14 |
|
$this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
287
|
14 |
|
$this->fieldNames[$mapping['api_field']] = $mapping['field']; |
288
|
|
|
|
289
|
|
|
// Complete id mapping |
290
|
14 |
View Code Duplication |
if (isset($mapping['id']) && $mapping['id'] === true) { |
|
|
|
|
291
|
14 |
|
if (!in_array($mapping['field'], $this->identifier, true)) { |
292
|
14 |
|
$this->identifier[] = $mapping['field']; |
293
|
14 |
|
} |
294
|
|
|
// Check for composite key |
295
|
14 |
|
if (!$this->isIdentifierComposite && count($this->identifier) > 1) { |
296
|
1 |
|
$this->isIdentifierComposite = true; |
297
|
1 |
|
} |
298
|
14 |
|
} |
299
|
|
|
|
300
|
14 |
|
} |
301
|
|
|
|
302
|
|
|
/** |
303
|
|
|
* @param string $fieldName |
304
|
|
|
* |
305
|
|
|
* @throws MappingException |
306
|
|
|
*/ |
307
|
14 |
|
private function assertFieldNotMapped($fieldName) |
308
|
|
|
{ |
309
|
14 |
|
if (array_key_exists($fieldName, $this->fields) || |
310
|
14 |
|
array_key_exists($fieldName, $this->associations) || |
311
|
14 |
|
array_key_exists($fieldName, $this->identifier) |
312
|
14 |
|
) { |
313
|
|
|
throw new MappingException('Field already mapped'); |
314
|
|
|
} |
315
|
14 |
|
} |
316
|
|
|
|
317
|
|
|
/** {@inheritdoc} */ |
318
|
4 |
|
public function getFieldMapping($fieldName) |
319
|
|
|
{ |
320
|
4 |
|
if (!isset($this->fields[$fieldName])) { |
321
|
|
|
throw MappingException::unknownField($fieldName, $this->getName()); |
322
|
|
|
} |
323
|
|
|
|
324
|
4 |
|
return $this->fields[$fieldName]; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** {@inheritdoc} */ |
328
|
9 |
|
public function getAssociationMapping($fieldName) |
329
|
|
|
{ |
330
|
9 |
|
if (!isset($this->associations[$fieldName])) { |
331
|
|
|
throw MappingException::unknownAssociation($fieldName, $this->getName()); |
332
|
|
|
} |
333
|
|
|
|
334
|
9 |
|
return $this->associations[$fieldName]; |
335
|
|
|
} |
336
|
|
|
|
337
|
2 |
|
public function setCustomRepositoryClass($customRepositoryClassName) |
338
|
|
|
{ |
339
|
2 |
|
$this->repositoryClass = $customRepositoryClassName; |
340
|
2 |
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* @internal |
344
|
|
|
* |
345
|
|
|
* @param array $mapping |
346
|
|
|
* |
347
|
|
|
* @return void |
348
|
|
|
*/ |
349
|
6 |
|
public function addInheritedFieldMapping(array $mapping) |
350
|
|
|
{ |
351
|
6 |
|
$this->fields[$mapping['field']] = $mapping; |
352
|
6 |
|
$this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
353
|
6 |
|
$this->fieldNames[$mapping['api_field']] = $mapping['field']; |
354
|
6 |
|
} |
355
|
|
|
|
356
|
|
|
/** {@inheritdoc} */ |
357
|
|
|
public function getFieldName($apiFieldName) |
358
|
|
|
{ |
359
|
|
|
return $this->fieldNames[$apiFieldName]; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** {@inheritdoc} */ |
363
|
13 |
|
public function getApiFieldName($fieldName) |
364
|
|
|
{ |
365
|
13 |
|
return $this->apiFieldNames[$fieldName]; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
public function hasApiField($apiFieldName) |
369
|
|
|
{ |
370
|
|
|
return array_key_exists($apiFieldName, $this->fieldNames); |
371
|
|
|
} |
372
|
|
|
|
373
|
10 |
|
public function mapAssociation(array $mapping) |
374
|
|
|
{ |
375
|
10 |
|
$mapping = $this->validateAndCompleteAssociationMapping($mapping); |
376
|
10 |
|
$this->assertFieldNotMapped($mapping['field']); |
377
|
10 |
|
$this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
378
|
10 |
|
$this->fieldNames[$mapping['api_field']] = $mapping['field']; |
379
|
10 |
|
$this->associations[$mapping['field']] = $mapping; |
380
|
10 |
|
} |
381
|
|
|
|
382
|
|
|
/** |
383
|
|
|
* Validates & completes the basic mapping information that is common to all |
384
|
|
|
* association mappings (one-to-one, many-ot-one, one-to-many, many-to-many). |
385
|
|
|
* |
386
|
|
|
* @param array $mapping The mapping. |
387
|
|
|
* |
388
|
|
|
* @return array The updated mapping. |
389
|
|
|
* |
390
|
|
|
* @throws MappingException If something is wrong with the mapping. |
391
|
|
|
*/ |
392
|
10 |
|
protected function validateAndCompleteAssociationMapping(array $mapping) |
393
|
|
|
{ |
394
|
10 |
|
if (!array_key_exists('api_field', $mapping)) { |
395
|
10 |
|
$mapping['api_field'] = $mapping['field']; |
396
|
10 |
|
} |
397
|
|
|
|
398
|
10 |
|
if (!isset($mapping['mappedBy'])) { |
399
|
10 |
|
$mapping['mappedBy'] = null; |
400
|
10 |
|
} |
401
|
|
|
|
402
|
10 |
|
if (!isset($mapping['inversedBy'])) { |
403
|
10 |
|
$mapping['inversedBy'] = null; |
404
|
10 |
|
} |
405
|
|
|
|
406
|
10 |
|
$mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy |
407
|
|
|
|
408
|
|
|
// unset optional indexBy attribute if its empty |
409
|
10 |
|
if (!isset($mapping['indexBy']) || !$mapping['indexBy']) { |
410
|
10 |
|
unset($mapping['indexBy']); |
411
|
10 |
|
} |
412
|
|
|
|
413
|
|
|
// If targetEntity is unqualified, assume it is in the same namespace as |
414
|
|
|
// the sourceEntity. |
415
|
10 |
|
$mapping['source'] = $this->name; |
416
|
10 |
|
if (isset($mapping['target'])) { |
417
|
10 |
|
$mapping['target'] = ltrim($mapping['target'], '\\'); |
418
|
10 |
|
} |
419
|
|
|
|
420
|
|
|
// Complete id mapping |
421
|
10 |
View Code Duplication |
if (isset($mapping['id']) && $mapping['id'] === true) { |
|
|
|
|
422
|
|
|
|
423
|
|
|
if (!in_array($mapping['field'], $this->identifier, true)) { |
424
|
|
|
$this->identifier[] = $mapping['field']; |
425
|
|
|
$this->containsForeignIdentifier = true; |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
// Check for composite key |
429
|
|
|
if (!$this->isIdentifierComposite && count($this->identifier) > 1) { |
430
|
|
|
$this->isIdentifierComposite = true; |
431
|
|
|
} |
432
|
|
|
} |
433
|
|
|
|
434
|
|
|
// Mandatory and optional attributes for either side |
435
|
10 |
|
if (null !== $mapping['mappedBy']) { |
436
|
10 |
|
$mapping['isOwningSide'] = false; |
437
|
10 |
|
} |
438
|
|
|
|
439
|
10 |
|
if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) { |
440
|
|
|
throw new MappingException( |
441
|
|
|
sprintf('Illegal toMany identifier association %s for %s', $mapping['fieldName'], $this->name) |
442
|
|
|
); |
443
|
|
|
} |
444
|
|
|
|
445
|
10 |
|
return $mapping; |
446
|
|
|
} |
447
|
|
|
|
448
|
|
|
/** {@inheritdoc} */ |
449
|
11 |
|
public function newInstance() |
450
|
|
|
{ |
451
|
11 |
|
return $this->instantiator->instantiate($this->name); |
452
|
|
|
} |
453
|
|
|
|
454
|
5 |
|
public function getSearcherClass() |
455
|
|
|
{ |
456
|
5 |
|
return $this->searcher; |
457
|
|
|
} |
458
|
|
|
|
459
|
11 |
|
public function getFinderClass() |
460
|
|
|
{ |
461
|
11 |
|
return $this->finder; |
462
|
|
|
} |
463
|
|
|
|
464
|
4 |
|
public function isIdentifierComposite() |
465
|
|
|
{ |
466
|
4 |
|
return $this->isIdentifierComposite; |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
/** {@inheritdoc} */ |
470
|
|
|
public function getRootEntityName() |
471
|
|
|
{ |
472
|
|
|
return $this->rootEntityName; |
473
|
|
|
} |
474
|
|
|
|
475
|
|
|
/** |
476
|
|
|
* Populates the entity identifier of an entity. |
477
|
|
|
* |
478
|
|
|
* @param object $entity |
479
|
|
|
* @param array $id |
480
|
|
|
* |
481
|
|
|
* @return void |
482
|
|
|
*/ |
483
|
|
|
public function assignIdentifier($entity, array $id) |
484
|
|
|
{ |
485
|
|
|
foreach ($id as $idField => $idValue) { |
486
|
|
|
$this->reflFields[$idField]->setValue($entity, $idValue); |
487
|
|
|
} |
488
|
|
|
} |
489
|
|
|
|
490
|
6 |
|
public function addInheritedAssociationMapping(array $mapping) |
491
|
|
|
{ |
492
|
6 |
|
$this->associations[$mapping['field']] = $mapping; |
493
|
6 |
|
$this->apiFieldNames[$mapping['field']] = $mapping['api_field']; |
494
|
6 |
|
$this->fieldNames[$mapping['api_field']] = $mapping['field']; |
495
|
6 |
|
} |
496
|
|
|
} |
497
|
|
|
|
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.