Completed
Push — master ( d466fc...57558f )
by Pavel
03:56
created

EntityMetadata::validateAndCompleteFieldMapping()   B

Complexity

Conditions 7
Paths 10

Size

Total Lines 21
Code Lines 10

Duplication

Lines 9
Ratio 42.86 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 9
loc 21
ccs 15
cts 15
cp 1
rs 7.551
cc 7
eloc 10
nc 10
nop 1
crap 7
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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();
0 ignored issues
show
Bug introduced by
Consider using $this->reflClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
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