Passed
Pull Request — master (#14)
by Pavel
03:16
created

EntityMetadata   D

Complexity

Total Complexity 147

Size/Duplication

Total Lines 826
Duplicated Lines 2.18 %

Coupling/Cohesion

Components 2
Dependencies 4

Test Coverage

Coverage 77.91%

Importance

Changes 0
Metric Value
wmc 147
lcom 2
cbo 4
dl 18
loc 826
ccs 268
cts 344
cp 0.7791
rs 4.4444
c 0
b 0
f 0

70 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A containsForeignIdentifier() 0 4 1
A getReflectionProperties() 0 4 1
A getReflectionProperty() 0 8 2
A getName() 0 4 1
A getMethodContainer() 0 4 1
A getRepositoryClass() 0 4 1
A getIdentifier() 0 4 1
A setIdentifier() 0 5 1
A getReflectionClass() 0 8 2
A isIdentifier() 0 4 1
A hasField() 0 4 1
A getFieldNames() 0 4 1
A hasAssociation() 0 4 1
A getAssociationNames() 0 4 1
A isSingleValuedAssociation() 0 4 2
A isCollectionValuedAssociation() 0 4 2
A getIdentifierFieldNames() 0 4 1
A getTypeOfField() 0 4 1
A getAssociationTargetClass() 0 4 1
A isAssociationInverseSide() 0 6 1
A getAssociationMappedByTargetField() 0 4 1
B getIdentifierValues() 0 21 5
B wakeupReflection() 8 16 6
A initializeReflection() 0 8 2
A getApiFactory() 0 8 2
A getClientName() 0 8 2
A mapField() 0 6 1
A getFieldMapping() 0 8 2
A getAssociationMapping() 0 8 2
A setCustomRepositoryClass() 0 4 1
A addInheritedFieldMapping() 0 6 1
A getFieldName() 0 4 1
A getApiFieldName() 0 4 1
A hasApiField() 0 4 1
A mapOneToMany() 0 6 1
A mapManyToOne() 0 6 1
A mapOneToOne() 0 6 1
A newInstance() 0 4 1
A isIdentifierComposite() 0 4 1
A getRootEntityName() 0 4 1
A assignIdentifier() 0 6 2
A addInheritedAssociationMapping() 0 6 1
A getSubclasses() 0 4 1
A getAssociationMappings() 0 4 1
A isReadOnly() 0 4 1
A setChangeTrackingPolicy() 0 4 1
A isChangeTrackingDeferredExplicit() 0 4 1
A isChangeTrackingDeferredImplicit() 0 4 1
A isChangeTrackingNotify() 0 4 1
A mapIdentifier() 0 6 1
A setIdGeneratorType() 0 4 1
A isIdentifierNatural() 0 4 1
A isIdentifierRemote() 0 4 1
A setIdentifierValues() 0 6 2
A isRootEntity() 0 4 1
B setDiscriminatorField() 0 21 7
A setDiscriminatorMap() 0 6 2
B addDiscriminatorMapClass() 0 17 6
B fullyQualifiedClassName() 0 10 5
F validateAndCompleteAssociationMapping() 10 89 25
A storeMapping() 0 8 1
C validateAndCompleteFieldMapping() 0 24 7
A assertFieldNotMapped() 0 9 4
A validateAndCompleteOneToManyMapping() 0 16 4
A assertMappingOrderBy() 0 8 3
A validateAndCompleteOneToOneMapping() 0 12 4
A getDiscriminatorField() 0 4 1
A getDiscriminatorMap() 0 4 1
A getDiscriminatorValue() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EntityMetadata often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EntityMetadata, and based on these observations, apply Extract Interface, too.

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 25
    public function __construct($entityName)
74
    {
75 25
        $this->name           = $entityName;
76 25
        $this->rootEntityName = $entityName;
77 25
    }
78
79
    /**
80
     * @return boolean
81
     */
82
    public function containsForeignIdentifier()
83
    {
84
        return $this->containsForeignIdentifier;
85
    }
86
87
    /** {@inheritdoc} */
88 10
    public function getReflectionProperties()
89
    {
90 10
        return $this->reflFields;
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96 22
    public function getReflectionProperty($name)
97
    {
98 22
        if (!array_key_exists($name, $this->reflFields)) {
99
            throw MappingException::noSuchProperty($name, $this->getName());
100
        }
101
102 22
        return $this->reflFields[$name];
103
    }
104
105
    /** {@inheritdoc} */
106 25
    public function getName()
107
    {
108 25
        return $this->name;
109
    }
110
111
    /** {@inheritdoc} */
112 24
    public function getMethodContainer()
113
    {
114 24
        return $this->methodProvider;
115
    }
116
117
    /** {@inheritdoc} */
118 16
    public function getRepositoryClass()
119
    {
120 16
        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 16
    public function getReflectionClass()
137
    {
138 16
        if (null === $this->reflClass) {
139
            $this->reflClass = new \ReflectionClass($this->getName());
140
        }
141
142 16
        return $this->reflClass;
143
    }
144
145
    /** {@inheritdoc} */
146 6
    public function isIdentifier($fieldName)
147
    {
148 6
        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 16
    public function getFieldNames()
159
    {
160 16
        return array_keys($this->fields);
161
    }
162
163
    /** {@inheritdoc} */
164 23
    public function hasAssociation($fieldName)
165
    {
166 23
        return in_array($fieldName, $this->getAssociationNames(), true);
167
    }
168
169
    /** {@inheritdoc} */
170 23
    public function getAssociationNames()
171
    {
172 23
        return array_keys($this->associations);
173
    }
174
175
    /** {@inheritdoc} */
176 11
    public function isSingleValuedAssociation($fieldName)
177
    {
178 11
        return $this->hasAssociation($fieldName) && $this->associations[$fieldName]['type'] & self::TO_ONE;
179
    }
180
181
    /** {@inheritdoc} */
182 14
    public function isCollectionValuedAssociation($fieldName)
183
    {
184 14
        return $this->hasAssociation($fieldName) && $this->associations[$fieldName]['type'] & self::TO_MANY;
185
    }
186
187
    /** {@inheritdoc} */
188 19
    public function getIdentifierFieldNames()
189
    {
190 19
        return $this->identifier;
191
    }
192
193
    /** {@inheritdoc} */
194 23
    public function getTypeOfField($fieldName)
195
    {
196 23
        return $this->fields[$fieldName]['type'];
197
    }
198
199
    /** {@inheritdoc} */
200 11
    public function getAssociationTargetClass($assocName)
201
    {
202 11
        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 20
    public function getIdentifierValues($object)
221
    {
222 20
        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 19
        $id    = $this->identifier[0];
234 19
        $value = $this->reflFields[$id]->getValue($object);
235 19
        if (null === $value) {
236
            return [];
237
        }
238
239 19
        return [$id => $value];
240
    }
241
242
    /** {@inheritdoc} */
243 25
    public function wakeupReflection(ReflectionService $reflService)
244
    {
245
        // Restore ReflectionClass and properties
246 25
        $this->reflClass    = $reflService->getClass($this->name);
247 25
        $this->instantiator = $this->instantiator ?: new Instantiator();
248
249 25 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...
250 25
            $class                    = array_key_exists('declared', $mapping) ? $mapping['declared'] : $this->name;
251 25
            $this->reflFields[$field] = $reflService->getAccessibleProperty($class, $field);
252 25
        }
253
254 25 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...
255 17
            $class                    = array_key_exists('declared', $mapping) ? $mapping['declared'] : $this->name;
256 17
            $this->reflFields[$field] = $reflService->getAccessibleProperty($class, $field);
257 25
        }
258 25
    }
259
260
    /** {@inheritdoc} */
261 25
    public function initializeReflection(ReflectionService $reflService)
262
    {
263 25
        $this->reflClass = $reflService->getClass($this->name);
264 25
        $this->namespace = $reflService->getClassNamespace($this->name);
265 25
        if ($this->reflClass) {
266 25
            $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...
267 25
        }
268 25
    }
269
270
    /** {@inheritdoc} */
271 23
    public function getApiFactory()
272
    {
273 23
        if (null === $this->apiFactory) {
274
            throw MappingException::noApiSpecified($this->getName());
275
        }
276
277 23
        return $this->apiFactory;
278
    }
279
280
    /** {@inheritdoc} */
281 24
    public function getClientName()
282
    {
283 24
        if (null === $this->clientName) {
284
            throw MappingException::noClientSpecified($this->getName());
285
        }
286
287 24
        return $this->clientName;
288
    }
289
290 25
    public function mapField(array $mapping)
291
    {
292 25
        $this->validateAndCompleteFieldMapping($mapping);
293 25
        $this->assertFieldNotMapped($mapping['field']);
294 25
        $this->fields[$mapping['field']] = $mapping;
295 25
    }
296
297
    /** {@inheritdoc} */
298 4
    public function getFieldMapping($fieldName)
299
    {
300 4
        if (!isset($this->fields[$fieldName])) {
301
            throw MappingException::unknownField($fieldName, $this->getName());
302
        }
303
304 4
        return $this->fields[$fieldName];
305
    }
306
307
    /** {@inheritdoc} */
308 16
    public function getAssociationMapping($fieldName)
309
    {
310 16
        if (!isset($this->associations[$fieldName])) {
311
            throw MappingException::unknownAssociation($fieldName, $this->getName());
312
        }
313
314 16
        return $this->associations[$fieldName];
315
    }
316
317 2
    public function setCustomRepositoryClass($customRepositoryClassName)
318
    {
319 2
        $this->repositoryClass = $customRepositoryClassName;
320 2
    }
321
322
    /**
323
     * @internal
324
     *
325
     * @param array $mapping
326
     *
327
     * @return void
328
     */
329 14
    public function addInheritedFieldMapping(array $mapping)
330
    {
331 14
        $this->fields[$mapping['field']]         = $mapping;
332 14
        $this->apiFieldNames[$mapping['field']]  = $mapping['api_field'];
333 14
        $this->fieldNames[$mapping['api_field']] = $mapping['field'];
334 14
    }
335
336
    /** {@inheritdoc} */
337 6
    public function getFieldName($apiFieldName)
338
    {
339 6
        return $this->fieldNames[$apiFieldName];
340
    }
341
342
    /** {@inheritdoc} */
343 23
    public function getApiFieldName($fieldName)
344
    {
345 23
        return $this->apiFieldNames[$fieldName];
346
    }
347
348
    public function hasApiField($apiFieldName)
349
    {
350
        return array_key_exists($apiFieldName, $this->fieldNames);
351
    }
352
353 17
    public function mapOneToMany(array $mapping)
354
    {
355 17
        $mapping = $this->validateAndCompleteOneToManyMapping($mapping);
356
357 17
        $this->storeMapping($mapping);
358 17
    }
359
360 17
    public function mapManyToOne(array $mapping)
361
    {
362 17
        $mapping = $this->validateAndCompleteOneToOneMapping($mapping);
363
364 17
        $this->storeMapping($mapping);
365 17
    }
366
367
    public function mapOneToOne(array $mapping)
368
    {
369
        $mapping = $this->validateAndCompleteOneToOneMapping($mapping);
370
371
        $this->storeMapping($mapping);
372
    }
373
374
    /** {@inheritdoc} */
375 15
    public function newInstance()
376
    {
377 15
        return $this->instantiator->instantiate($this->name);
378
    }
379
380 7
    public function isIdentifierComposite()
381
    {
382 7
        return $this->isIdentifierComposite;
383
    }
384
385
    /** {@inheritdoc} */
386 25
    public function getRootEntityName()
387
    {
388 25
        return $this->rootEntityName;
389
    }
390
391
    /**
392
     * Populates the entity identifier of an entity.
393
     *
394
     * @param object $entity
395
     * @param array  $id
396
     *
397
     * @return void
398
     */
399
    public function assignIdentifier($entity, array $id)
400
    {
401
        foreach ($id as $idField => $idValue) {
402
            $this->reflFields[$idField]->setValue($entity, $idValue);
403
        }
404
    }
405
406 10
    public function addInheritedAssociationMapping(array $mapping)
407
    {
408 10
        $this->associations[$mapping['field']]   = $mapping;
409 10
        $this->apiFieldNames[$mapping['field']]  = $mapping['api_field'];
410 10
        $this->fieldNames[$mapping['api_field']] = $mapping['field'];
411 10
    }
412
413
    /** {@inheritdoc} */
414 7
    public function getSubclasses()
415
    {
416 7
        return $this->subclasses;
417
    }
418
419
    /** {@inheritdoc} */
420 6
    public function getAssociationMappings()
421
    {
422 6
        return $this->associations;
423
    }
424
425 3
    public function isReadOnly()
426
    {
427 3
        return false;
428
    }
429
430
    /**
431
     * Sets the change tracking policy used by this class.
432
     *
433
     * @param integer $policy
434
     *
435
     * @return void
436
     */
437
    public function setChangeTrackingPolicy($policy)
438
    {
439
        $this->changeTrackingPolicy = $policy;
440
    }
441
442
    /**
443
     * Whether the change tracking policy of this class is "deferred explicit".
444
     *
445
     * @return boolean
446
     */
447
    public function isChangeTrackingDeferredExplicit()
448
    {
449
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_EXPLICIT;
450
    }
451
452
    /**
453
     * Whether the change tracking policy of this class is "deferred implicit".
454
     *
455
     * @return boolean
456
     */
457 3
    public function isChangeTrackingDeferredImplicit()
458
    {
459 3
        return $this->changeTrackingPolicy === self::CHANGETRACKING_DEFERRED_IMPLICIT;
460
    }
461
462
    /**
463
     * Whether the change tracking policy of this class is "notify".
464
     *
465
     * @return boolean
466
     */
467
    public function isChangeTrackingNotify()
468
    {
469
        return $this->changeTrackingPolicy === self::CHANGETRACKING_NOTIFY;
470
    }
471
472 25
    public function mapIdentifier(array $mapping)
473
    {
474 25
        $this->setIdGeneratorType($mapping['generator']['strategy']);
475
476 25
        $this->mapField($mapping);
477 25
    }
478
479
    /**
480
     * Sets the type of Id generator to use for the mapped class.
481
     *
482
     * @param int $generatorType
483
     *
484
     * @return void
485
     */
486 25
    public function setIdGeneratorType($generatorType)
487
    {
488 25
        $this->generatorType = $generatorType;
489 25
    }
490
491 6
    public function isIdentifierNatural()
492
    {
493 6
        return $this->generatorType === self::GENERATOR_TYPE_NATURAL;
494
    }
495
496 6
    public function isIdentifierRemote()
497
    {
498 6
        return $this->generatorType === self::GENERATOR_TYPE_REMOTE;
499
    }
500
501
    /**
502
     * Populates the entity identifier of an entity.
503
     *
504
     * @param object $entity
505
     * @param array  $id
506
     *
507
     * @return void
508
     *
509
     * @todo Rename to assignIdentifier()
510
     */
511
    public function setIdentifierValues($entity, array $id)
512
    {
513
        foreach ($id as $idField => $idValue) {
514
            $this->reflFields[$idField]->setValue($entity, $idValue);
515
        }
516
    }
517
518 25
    public function isRootEntity()
519
    {
520 25
        return $this->getRootEntityName() === $this->getName();
521
    }
522
523
    /**
524
     * Sets the discriminator column definition.
525
     *
526
     * @param array $columnDef
527
     *
528
     * @return void
529
     *
530
     * @throws MappingException
531
     *
532
     * @see getDiscriminatorColumn()
533
     */
534 14
    public function setDiscriminatorField(array $columnDef = null)
535
    {
536 14
        if ($columnDef !== null) {
537 4
            if (!isset($columnDef['name'])) {
538
                throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name);
539
            }
540 4
            if (isset($this->fieldNames[$columnDef['name']])) {
541
                throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
542
            }
543 4
            if (!isset($columnDef['fieldName'])) {
544 4
                $columnDef['fieldName'] = $columnDef['name'];
545 4
            }
546 4
            if (!isset($columnDef['type'])) {
547
                $columnDef['type'] = 'string';
548
            }
549 4
            if (in_array($columnDef['type'], ['boolean', 'array', 'object', 'datetime', 'time', 'date'], true)) {
550
                throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
551
            }
552 4
            $this->discriminatorField = $columnDef;
0 ignored issues
show
Documentation Bug introduced by
It seems like $columnDef of type array<string,?,{"type":"string"}> is incompatible with the declared type array<integer,string> of property $discriminatorField.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
553 4
        }
554 14
    }
555
556
    /**
557
     * Sets the discriminator values used by this class.
558
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
559
     *
560
     * @param array $map
561
     *
562
     * @return void
563
     */
564 25
    public function setDiscriminatorMap(array $map)
565
    {
566 25
        foreach ($map as $value => $className) {
567 25
            $this->addDiscriminatorMapClass($value, $className);
568 25
        }
569 25
    }
570
571
    /**
572
     * Adds one entry of the discriminator map with a new class and corresponding name.
573
     *
574
     * @param string $name
575
     * @param string $className
576
     *
577
     * @return void
578
     *
579
     * @throws MappingException
580
     */
581 25
    public function addDiscriminatorMapClass($name, $className)
582
    {
583 25
        $className                     = $this->fullyQualifiedClassName($className);
584 25
        $className                     = ltrim($className, '\\');
585 25
        $this->discriminatorMap[$name] = $className;
586 25
        if ($this->name === $className) {
587 25
            $this->discriminatorValue = $name;
588
589 25
            return;
590
        }
591 20
        if (!(class_exists($className) || interface_exists($className))) {
592
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
593
        }
594 20
        if (is_subclass_of($className, $this->name) && !in_array($className, $this->subclasses)) {
1 ignored issue
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->name can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
595 20
            $this->subclasses[] = $className;
596 20
        }
597 20
    }
598
599
    /**
600
     * @param  string|null $className
601
     *
602
     * @return string|null null if the input value is null
603
     */
604 25
    public function fullyQualifiedClassName($className)
605
    {
606 25
        if (empty($className)) {
607
            return $className;
608
        }
609 25
        if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
610
            return $this->namespace . '\\' . $className;
611
        }
612 25
        return $className;
613
    }
614
615
    /**
616
     * Validates & completes the basic mapping information that is common to all
617
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
618
     *
619
     * @param array $mapping The mapping.
620
     *
621
     * @return array The updated mapping.
622
     *
623
     * @throws MappingException If something is wrong with the mapping.
624
     */
625 17
    protected function validateAndCompleteAssociationMapping(array $mapping)
626
    {
627 17
        if (!array_key_exists('api_field', $mapping)) {
628 17
            $mapping['api_field'] = $mapping['field'];
629 17
        }
630
631 17
        if (!isset($mapping['mappedBy'])) {
632 17
            $mapping['mappedBy'] = null;
633 17
        }
634
635 17
        if (!isset($mapping['inversedBy'])) {
636 17
            $mapping['inversedBy'] = null;
637 17
        }
638
639 17
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
640
641
        // unset optional indexBy attribute if its empty
642 17
        if (!isset($mapping['indexBy']) || !$mapping['indexBy']) {
643 17
            unset($mapping['indexBy']);
644 17
        }
645
646
        // If targetEntity is unqualified, assume it is in the same namespace as
647
        // the sourceEntity.
648 17
        $mapping['source'] = $this->name;
649 17
        if (isset($mapping['target'])) {
650 17
            $mapping['target'] = ltrim($mapping['target'], '\\');
651 17
        }
652
653 17
        if (($mapping['type'] & self::MANY_TO_ONE) > 0 &&
654 17
            isset($mapping['orphanRemoval']) &&
655
            $mapping['orphanRemoval'] == true
656 17
        ) {
657
            throw new MappingException(
658
                sprintf('Illegal orphanRemoval %s for %s', $mapping['field'], $this->name)
659
            );
660
        }
661
662
        // Complete id mapping
663 17
        if (isset($mapping['id']) && $mapping['id'] === true) {
664 View Code Duplication
            if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == 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...
665
                throw new MappingException(
666
                    sprintf('Illegal orphanRemoval on identifier association %s for %s', $mapping['field'], $this->name)
667
                );
668
            }
669
670
            if (!in_array($mapping['field'], $this->identifier, true)) {
671
                $this->identifier[]              = $mapping['field'];
672
                $this->containsForeignIdentifier = true;
673
            }
674
675
            // Check for composite key
676
            if (!$this->isIdentifierComposite && count($this->identifier) > 1) {
677
                $this->isIdentifierComposite = true;
678
            }
679
        }
680
681
        // Mandatory and optional attributes for either side
682 17
        if (null !== $mapping['mappedBy']) {
683 17
            $mapping['isOwningSide'] = false;
684 17
        }
685
686 17 View Code Duplication
        if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) {
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...
687
            throw new MappingException(
688
                sprintf('Illegal toMany identifier association %s for %s', $mapping['field'], $this->name)
689
            );
690
        }
691
692
        // Fetch mode. Default fetch mode to LAZY, if not set.
693 17
        if (!isset($mapping['fetch'])) {
694 17
            $mapping['fetch'] = self::FETCH_LAZY;
695 17
        }
696
697
        // Cascades
698 17
        $cascades    = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
699 17
        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
700 17
        if (in_array('all', $cascades, true)) {
701
            $cascades = $allCascades;
702 17
        } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
703
            throw new MappingException('Invalid cascades: ' . implode(', ', $cascades));
704
        }
705 17
        $mapping['cascade']          = $cascades;
706 17
        $mapping['isCascadeRemove']  = in_array('remove', $cascades, true);
707 17
        $mapping['isCascadePersist'] = in_array('persist', $cascades, true);
708 17
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades, true);
709 17
        $mapping['isCascadeMerge']   = in_array('merge', $cascades, true);
710 17
        $mapping['isCascadeDetach']  = in_array('detach', $cascades, true);
711
712 17
        return $mapping;
713
    }
714
715 17
    private function storeMapping(array $mapping)
716
    {
717 17
        $this->assertFieldNotMapped($mapping['field']);
718
719 17
        $this->apiFieldNames[$mapping['field']]  = $mapping['api_field'];
720 17
        $this->fieldNames[$mapping['api_field']] = $mapping['field'];
721 17
        $this->associations[$mapping['field']]   = $mapping;
722 17
    }
723
724 25
    private function validateAndCompleteFieldMapping(array &$mapping)
725
    {
726 25
        if (!array_key_exists('api_field', $mapping)) {
727 24
            $mapping['api_field'] = $mapping['field']; //todo: invent naming strategy
728 24
        }
729
730 25
        $this->apiFieldNames[$mapping['field']]  = $mapping['api_field'];
731 25
        $this->fieldNames[$mapping['api_field']] = $mapping['field'];
732
733
//        if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorField && $this->discriminatorField['name'] === $mapping['api_field'])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
734
//            throw MappingException::duplicateColumnName($this->name, $mapping['columnName']);
735
//        }
736
737
        // Complete id mapping
738 25
        if (isset($mapping['id']) && $mapping['id'] === true) {
739 25
            if (!in_array($mapping['field'], $this->identifier, true)) {
740 25
                $this->identifier[] = $mapping['field'];
741 25
            }
742
            // Check for composite key
743 25
            if (!$this->isIdentifierComposite && count($this->identifier) > 1) {
744 1
                $this->isIdentifierComposite = true;
745 1
            }
746 25
        }
747 25
    }
748
749
    /**
750
     * @param string $fieldName
751
     *
752
     * @throws MappingException
753
     */
754 25
    private function assertFieldNotMapped($fieldName)
755
    {
756 25
        if (array_key_exists($fieldName, $this->fields) ||
757 25
            array_key_exists($fieldName, $this->associations) ||
758 25
            array_key_exists($fieldName, $this->identifier)
759 25
        ) {
760
            throw new MappingException('Field already mapped');
761
        }
762 25
    }
763
764
    /**
765
     * @param array $mapping
766
     *
767
     * @return array
768
     * @throws MappingException
769
     * @throws \InvalidArgumentException
770
     */
771 17
    private function validateAndCompleteOneToManyMapping(array $mapping)
772
    {
773 17
        $mapping = $this->validateAndCompleteAssociationMapping($mapping);
774
775
        // OneToMany-side MUST be inverse (must have mappedBy)
776 17
        if (!isset($mapping['mappedBy'])) {
777
            throw new MappingException(
778
                sprintf('Many to many requires mapped by: %s', $mapping['field'])
779
            );
780
        }
781 17
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
782 17
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
783 17
        $this->assertMappingOrderBy($mapping);
784
785 17
        return $mapping;
786
    }
787
788
    /**
789
     * @param array $mapping
790
     *
791
     * @throws \InvalidArgumentException
792
     */
793 17
    private function assertMappingOrderBy(array $mapping)
794
    {
795 17
        if (array_key_exists('orderBy', $mapping) && !is_array($mapping['orderBy'])) {
796
            throw new \InvalidArgumentException(
797
                "'orderBy' is expected to be an array, not " . gettype($mapping['orderBy'])
798
            );
799
        }
800 17
    }
801
802
    /**
803
     * @param array $mapping
804
     *
805
     * @return array
806
     */
807 17
    private function validateAndCompleteOneToOneMapping(array $mapping)
808
    {
809 17
        $mapping = $this->validateAndCompleteAssociationMapping($mapping);
810
811 17
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
812 17
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
813 17
        if ($mapping['orphanRemoval']) {
814
            unset($mapping['unique']);
815
        }
816
817 17
        return $mapping;
818
    }
819
820
    /** {@inheritdoc} */
821 23
    public function getDiscriminatorField()
822
    {
823 23
        return $this->discriminatorField;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->discriminatorField; (string[]) is incompatible with the return type declared by the interface Bankiru\Api\Doctrine\Map...::getDiscriminatorField of type string[]|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
824
    }
825
826
    /** {@inheritdoc} */
827 16
    public function getDiscriminatorMap()
828
    {
829 16
        return $this->discriminatorMap;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->discriminatorMap; (string[]) is incompatible with the return type declared by the interface Bankiru\Api\Doctrine\Map...ta::getDiscriminatorMap of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
830
    }
831
832
    /** {@inheritdoc} */
833 17
    public function getDiscriminatorValue()
834
    {
835 17
        return $this->discriminatorValue;
836
    }
837
}
838