Passed
Push — master ( e75d59...99d075 )
by Pavel
03:26
created

EntityMetadata::addDiscriminatorMapClass()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 26
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7.0119

Importance

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