Passed
Pull Request — master (#14)
by Pavel
05:12
created

EntityMetadata::getReflectionClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.2559

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 3
cts 5
cp 0.6
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
crap 2.2559
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 24
    public function __construct($entityName)
74
    {
75 24
        $this->name           = $entityName;
76 24
        $this->rootEntityName = $entityName;
77 24
    }
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 21
    public function getReflectionProperty($name)
97
    {
98 21
        if (!array_key_exists($name, $this->reflFields)) {
99
            throw MappingException::noSuchProperty($name, $this->getName());
100
        }
101
102 21
        return $this->reflFields[$name];
103
    }
104
105
    /** {@inheritdoc} */
106 24
    public function getName()
107
    {
108 24
        return $this->name;
109
    }
110
111
    /** {@inheritdoc} */
112 23
    public function getMethodContainer()
113
    {
114 23
        return $this->methodProvider;
115
    }
116
117
    /** {@inheritdoc} */
118 15
    public function getRepositoryClass()
119
    {
120 15
        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 15
    public function getReflectionClass()
137
    {
138 15
        if (null === $this->reflClass) {
139
            $this->reflClass = new \ReflectionClass($this->getName());
140
        }
141
142 15
        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 15
    public function getFieldNames()
159
    {
160 15
        return array_keys($this->fields);
161
    }
162
163
    /** {@inheritdoc} */
164 22
    public function hasAssociation($fieldName)
165
    {
166 22
        return in_array($fieldName, $this->getAssociationNames(), true);
167
    }
168
169
    /** {@inheritdoc} */
170 22
    public function getAssociationNames()
171
    {
172 22
        return array_keys($this->associations);
173
    }
174
175
    /** {@inheritdoc} */
176 10
    public function isSingleValuedAssociation($fieldName)
177
    {
178 10
        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 18
    public function getIdentifierFieldNames()
189
    {
190 18
        return $this->identifier;
191
    }
192
193
    /** {@inheritdoc} */
194 22
    public function getTypeOfField($fieldName)
195
    {
196 22
        return $this->fields[$fieldName]['type'];
197
    }
198
199
    /** {@inheritdoc} */
200 10
    public function getAssociationTargetClass($assocName)
201
    {
202 10
        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 19
    public function getIdentifierValues($object)
221
    {
222 19
        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 18
        $id    = $this->identifier[0];
234 18
        $value = $this->reflFields[$id]->getValue($object);
235 18
        if (null === $value) {
236
            return [];
237
        }
238
239 18
        return [$id => $value];
240
    }
241
242
    /** {@inheritdoc} */
243 24
    public function wakeupReflection(ReflectionService $reflService)
244
    {
245
        // Restore ReflectionClass and properties
246 24
        $this->reflClass    = $reflService->getClass($this->name);
247 24
        $this->instantiator = $this->instantiator ?: new Instantiator();
248
249 24 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 24
            $class                    = array_key_exists('declared', $mapping) ? $mapping['declared'] : $this->name;
251 24
            $this->reflFields[$field] = $reflService->getAccessibleProperty($class, $field);
252 24
        }
253
254 24 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 16
            $class                    = array_key_exists('declared', $mapping) ? $mapping['declared'] : $this->name;
256 16
            $this->reflFields[$field] = $reflService->getAccessibleProperty($class, $field);
257 24
        }
258 24
    }
259
260
    /** {@inheritdoc} */
261 24
    public function initializeReflection(ReflectionService $reflService)
262
    {
263 24
        $this->reflClass = $reflService->getClass($this->name);
264 24
        $this->namespace = $reflService->getClassNamespace($this->name);
265 24
        if ($this->reflClass) {
266 24
            $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 24
        }
268 24
    }
269
270
    /** {@inheritdoc} */
271 22
    public function getApiFactory()
272
    {
273 22
        if (null === $this->apiFactory) {
274
            throw MappingException::noApiSpecified($this->getName());
275
        }
276
277 22
        return $this->apiFactory;
278
    }
279
280
    /** {@inheritdoc} */
281 23
    public function getClientName()
282
    {
283 23
        if (null === $this->clientName) {
284
            throw MappingException::noClientSpecified($this->getName());
285
        }
286
287 23
        return $this->clientName;
288
    }
289
290 24
    public function mapField(array $mapping)
291
    {
292 24
        $this->validateAndCompleteFieldMapping($mapping);
293 24
        $this->assertFieldNotMapped($mapping['field']);
294 24
        $this->fields[$mapping['field']] = $mapping;
295 24
    }
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 15
    public function getAssociationMapping($fieldName)
309
    {
310 15
        if (!isset($this->associations[$fieldName])) {
311
            throw MappingException::unknownAssociation($fieldName, $this->getName());
312
        }
313
314 15
        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 22
    public function getApiFieldName($fieldName)
344
    {
345 22
        return $this->apiFieldNames[$fieldName];
346
    }
347
348
    public function hasApiField($apiFieldName)
349
    {
350
        return array_key_exists($apiFieldName, $this->fieldNames);
351
    }
352
353 16
    public function mapOneToMany(array $mapping)
354
    {
355 16
        $mapping = $this->validateAndCompleteOneToManyMapping($mapping);
356
357 16
        $this->storeMapping($mapping);
358 16
    }
359
360 16
    public function mapManyToOne(array $mapping)
361
    {
362 16
        $mapping = $this->validateAndCompleteOneToOneMapping($mapping);
363
364 16
        $this->storeMapping($mapping);
365 16
    }
366
367
    public function mapOneToOne(array $mapping)
368
    {
369
        $mapping = $this->validateAndCompleteOneToOneMapping($mapping);
370
371
        $this->storeMapping($mapping);
372
    }
373
374
    /** {@inheritdoc} */
375 14
    public function newInstance()
376
    {
377 14
        return $this->instantiator->instantiate($this->name);
378
    }
379
380 7
    public function isIdentifierComposite()
381
    {
382 7
        return $this->isIdentifierComposite;
383
    }
384
385
    /** {@inheritdoc} */
386 24
    public function getRootEntityName()
387
    {
388 24
        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 24
    public function mapIdentifier(array $mapping)
473
    {
474 24
        $this->setIdGeneratorType($mapping['generator']['strategy']);
475
476 24
        $this->mapField($mapping);
477 24
    }
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 24
    public function setIdGeneratorType($generatorType)
487
    {
488 24
        $this->generatorType = $generatorType;
489 24
    }
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 24
    public function isRootEntity()
519
    {
520 24
        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 24
    public function setDiscriminatorMap(array $map)
565
    {
566 24
        foreach ($map as $value => $className) {
567 24
            $this->addDiscriminatorMapClass($value, $className);
568 24
        }
569 24
    }
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 24
    public function addDiscriminatorMapClass($name, $className)
582
    {
583 24
        $className                     = $this->fullyQualifiedClassName($className);
584 24
        $className                     = ltrim($className, '\\');
585 24
        $this->discriminatorMap[$name] = $className;
586 24
        if ($this->name === $className) {
587 24
            $this->discriminatorValue = $name;
588
589 24
            return;
590
        }
591 19
        if (!(class_exists($className) || interface_exists($className))) {
592
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
593
        }
594 19
        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 19
            $this->subclasses[] = $className;
596 19
        }
597 19
    }
598
599
    /**
600
     * @param  string|null $className
601
     *
602
     * @return string|null null if the input value is null
603
     */
604 24
    public function fullyQualifiedClassName($className)
605
    {
606 24
        if (empty($className)) {
607
            return $className;
608
        }
609 24
        if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
610
            return $this->namespace . '\\' . $className;
611
        }
612 24
        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 16
    protected function validateAndCompleteAssociationMapping(array $mapping)
626
    {
627 16
        if (!array_key_exists('api_field', $mapping)) {
628 16
            $mapping['api_field'] = $mapping['field'];
629 16
        }
630
631 16
        if (!isset($mapping['mappedBy'])) {
632 16
            $mapping['mappedBy'] = null;
633 16
        }
634
635 16
        if (!isset($mapping['inversedBy'])) {
636 16
            $mapping['inversedBy'] = null;
637 16
        }
638
639 16
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
640
641
        // unset optional indexBy attribute if its empty
642 16
        if (!isset($mapping['indexBy']) || !$mapping['indexBy']) {
643 16
            unset($mapping['indexBy']);
644 16
        }
645
646
        // If targetEntity is unqualified, assume it is in the same namespace as
647
        // the sourceEntity.
648 16
        $mapping['source'] = $this->name;
649 16
        if (isset($mapping['target'])) {
650 16
            $mapping['target'] = ltrim($mapping['target'], '\\');
651 16
        }
652
653 16
        if (($mapping['type'] & self::MANY_TO_ONE) > 0 &&
654 16
            isset($mapping['orphanRemoval']) &&
655
            $mapping['orphanRemoval'] == true
656 16
        ) {
657
            throw new MappingException(
658
                sprintf('Illegal orphanRemoval %s for %s', $mapping['field'], $this->name)
659
            );
660
        }
661
662
        // Complete id mapping
663 16
        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 16
        if (null !== $mapping['mappedBy']) {
683 16
            $mapping['isOwningSide'] = false;
684 16
        }
685
686 16 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 16
        if (!isset($mapping['fetch'])) {
694 16
            $mapping['fetch'] = self::FETCH_LAZY;
695 16
        }
696
697
        // Cascades
698 16
        $cascades    = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
699 16
        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
700 16
        if (in_array('all', $cascades, true)) {
701
            $cascades = $allCascades;
702 16
        } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
703
            throw new MappingException('Invalid cascades: ' . implode(', ', $cascades));
704
        }
705 16
        $mapping['cascade']          = $cascades;
706 16
        $mapping['isCascadeRemove']  = in_array('remove', $cascades, true);
707 16
        $mapping['isCascadePersist'] = in_array('persist', $cascades, true);
708 16
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades, true);
709 16
        $mapping['isCascadeMerge']   = in_array('merge', $cascades, true);
710 16
        $mapping['isCascadeDetach']  = in_array('detach', $cascades, true);
711
712 16
        return $mapping;
713
    }
714
715 16
    private function storeMapping(array $mapping)
716
    {
717 16
        $this->assertFieldNotMapped($mapping['field']);
718
719 16
        $this->apiFieldNames[$mapping['field']]  = $mapping['api_field'];
720 16
        $this->fieldNames[$mapping['api_field']] = $mapping['field'];
721 16
        $this->associations[$mapping['field']]   = $mapping;
722 16
    }
723
724 24
    private function validateAndCompleteFieldMapping(array &$mapping)
725
    {
726 24
        if (!array_key_exists('api_field', $mapping)) {
727 23
            $mapping['api_field'] = $mapping['field']; //todo: invent naming strategy
728 23
        }
729
730 24
        $this->apiFieldNames[$mapping['field']]  = $mapping['api_field'];
731 24
        $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 24
        if (isset($mapping['id']) && $mapping['id'] === true) {
739 24
            if (!in_array($mapping['field'], $this->identifier, true)) {
740 24
                $this->identifier[] = $mapping['field'];
741 24
            }
742
            // Check for composite key
743 24
            if (!$this->isIdentifierComposite && count($this->identifier) > 1) {
744 1
                $this->isIdentifierComposite = true;
745 1
            }
746 24
        }
747 24
    }
748
749
    /**
750
     * @param string $fieldName
751
     *
752
     * @throws MappingException
753
     */
754 24
    private function assertFieldNotMapped($fieldName)
755
    {
756 24
        if (array_key_exists($fieldName, $this->fields) ||
757 24
            array_key_exists($fieldName, $this->associations) ||
758 24
            array_key_exists($fieldName, $this->identifier)
759 24
        ) {
760
            throw new MappingException('Field already mapped');
761
        }
762 24
    }
763
764
    /**
765
     * @param array $mapping
766
     *
767
     * @return array
768
     * @throws MappingException
769
     * @throws \InvalidArgumentException
770
     */
771 16
    private function validateAndCompleteOneToManyMapping(array $mapping)
772
    {
773 16
        $mapping = $this->validateAndCompleteAssociationMapping($mapping);
774
775
        // OneToMany-side MUST be inverse (must have mappedBy)
776 16
        if (!isset($mapping['mappedBy'])) {
777
            throw new MappingException(
778
                sprintf('Many to many requires mapped by: %s', $mapping['field'])
779
            );
780
        }
781 16
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
782 16
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
783 16
        $this->assertMappingOrderBy($mapping);
784
785 16
        return $mapping;
786
    }
787
788
    /**
789
     * @param array $mapping
790
     *
791
     * @throws \InvalidArgumentException
792
     */
793 16
    private function assertMappingOrderBy(array $mapping)
794
    {
795 16
        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 16
    }
801
802
    /**
803
     * @param array $mapping
804
     *
805
     * @return array
806
     */
807 16
    private function validateAndCompleteOneToOneMapping(array $mapping)
808
    {
809 16
        $mapping = $this->validateAndCompleteAssociationMapping($mapping);
810
811 16
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
812 16
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
813 16
        if ($mapping['orphanRemoval']) {
814
            unset($mapping['unique']);
815
        }
816
817 16
        return $mapping;
818
    }
819
820
    /** {@inheritdoc} */
821 22
    public function getDiscriminatorField()
822
    {
823 22
        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 15
    public function getDiscriminatorMap()
828
    {
829 15
        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 16
    public function getDiscriminatorValue()
834
    {
835 16
        return $this->discriminatorValue;
836
    }
837
}
838