Failed Conditions
Pull Request — master (#6886)
by Grégoire
63:05
created

ClassMetadata::setDiscriminatorColumn()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 12
nc 11
nop 1
dl 0
loc 24
rs 6.7272
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 51 and the first side effect is on line 3401.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Mapping;
21
22
use BadMethodCallException;
23
use Doctrine\Instantiator\Instantiator;
24
use InvalidArgumentException;
25
use RuntimeException;
26
use Doctrine\DBAL\Types\Type;
27
use Doctrine\DBAL\Platforms\AbstractPlatform;
28
use ReflectionClass;
29
use Doctrine\Common\Persistence\Mapping\ClassMetadata as BaseClassMetadata;
30
use Doctrine\Common\ClassLoader;
31
use Doctrine\ORM\Cache\CacheException;
32
33
/**
34
 * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
35
 * of an entity and its associations.
36
 *
37
 * Once populated, ClassMetadata instances are usually cached in a serialized form.
38
 *
39
 * <b>IMPORTANT NOTE:</b>
40
 *
41
 * The fields of this class are only public for 2 reasons:
42
 * 1) To allow fast READ access.
43
 * 2) To drastically reduce the size of a serialized instance (private/protected members
44
 *    get the whole class name, namespace inclusive, prepended to every property in
45
 *    the serialized representation).
46
 *
47
 * @author Roman Borschel <[email protected]>
48
 * @author Jonathan H. Wage <[email protected]>
49
 * @since 2.0
50
 */
51
class ClassMetadata implements BaseClassMetadata
52
{
53
    /* The inheritance mapping types */
54
    /**
55
     * NONE means the class does not participate in an inheritance hierarchy
56
     * and therefore does not need an inheritance mapping type.
57
     */
58
    const INHERITANCE_TYPE_NONE = 1;
59
60
    /**
61
     * JOINED means the class will be persisted according to the rules of
62
     * <tt>Class Table Inheritance</tt>.
63
     */
64
    const INHERITANCE_TYPE_JOINED = 2;
65
66
    /**
67
     * SINGLE_TABLE means the class will be persisted according to the rules of
68
     * <tt>Single Table Inheritance</tt>.
69
     */
70
    const INHERITANCE_TYPE_SINGLE_TABLE = 3;
71
72
    /**
73
     * TABLE_PER_CLASS means the class will be persisted according to the rules
74
     * of <tt>Concrete Table Inheritance</tt>.
75
     */
76
    const INHERITANCE_TYPE_TABLE_PER_CLASS = 4;
77
78
    /* The Id generator types. */
79
    /**
80
     * AUTO means the generator type will depend on what the used platform prefers.
81
     * Offers full portability.
82
     */
83
    const GENERATOR_TYPE_AUTO = 1;
84
85
    /**
86
     * SEQUENCE means a separate sequence object will be used. Platforms that do
87
     * not have native sequence support may emulate it. Full portability is currently
88
     * not guaranteed.
89
     */
90
    const GENERATOR_TYPE_SEQUENCE = 2;
91
92
    /**
93
     * TABLE means a separate table is used for id generation.
94
     * Offers full portability.
95
     */
96
    const GENERATOR_TYPE_TABLE = 3;
97
98
    /**
99
     * IDENTITY means an identity column is used for id generation. The database
100
     * will fill in the id column on insertion. Platforms that do not support
101
     * native identity columns may emulate them. Full portability is currently
102
     * not guaranteed.
103
     */
104
    const GENERATOR_TYPE_IDENTITY = 4;
105
106
    /**
107
     * NONE means the class does not have a generated id. That means the class
108
     * must have a natural, manually assigned id.
109
     */
110
    const GENERATOR_TYPE_NONE = 5;
111
112
    /**
113
     * UUID means that a UUID/GUID expression is used for id generation. Full
114
     * portability is currently not guaranteed.
115
     */
116
    const GENERATOR_TYPE_UUID = 6;
117
118
    /**
119
     * CUSTOM means that customer will use own ID generator that supposedly work
120
     */
121
    const GENERATOR_TYPE_CUSTOM = 7;
122
123
    /**
124
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
125
     * by doing a property-by-property comparison with the original data. This will
126
     * be done for all entities that are in MANAGED state at commit-time.
127
     *
128
     * This is the default change tracking policy.
129
     */
130
    const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
131
132
    /**
133
     * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
134
     * by doing a property-by-property comparison with the original data. This will
135
     * be done only for entities that were explicitly saved (through persist() or a cascade).
136
     */
137
    const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
138
139
    /**
140
     * NOTIFY means that Doctrine relies on the entities sending out notifications
141
     * when their properties change. Such entity classes must implement
142
     * the <tt>NotifyPropertyChanged</tt> interface.
143
     */
144
    const CHANGETRACKING_NOTIFY = 3;
145
146
    /**
147
     * Specifies that an association is to be fetched when it is first accessed.
148
     */
149
    const FETCH_LAZY = 2;
150
151
    /**
152
     * Specifies that an association is to be fetched when the owner of the
153
     * association is fetched.
154
     */
155
    const FETCH_EAGER = 3;
156
157
    /**
158
     * Specifies that an association is to be fetched lazy (on first access) and that
159
     * commands such as Collection#count, Collection#slice are issued directly against
160
     * the database if the collection is not yet initialized.
161
     */
162
    const FETCH_EXTRA_LAZY = 4;
163
164
    /**
165
     * Identifies a one-to-one association.
166
     */
167
    const ONE_TO_ONE = 1;
168
169
    /**
170
     * Identifies a many-to-one association.
171
     */
172
    const MANY_TO_ONE = 2;
173
174
    /**
175
     * Identifies a one-to-many association.
176
     */
177
    const ONE_TO_MANY = 4;
178
179
    /**
180
     * Identifies a many-to-many association.
181
     */
182
    const MANY_TO_MANY = 8;
183
184
    /**
185
     * Combined bitmask for to-one (single-valued) associations.
186
     */
187
    const TO_ONE = 3;
188
189
    /**
190
     * Combined bitmask for to-many (collection-valued) associations.
191
     */
192
    const TO_MANY = 12;
193
194
    /**
195
     * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks,
196
     */
197
    const CACHE_USAGE_READ_ONLY = 1;
198
199
    /**
200
     * Nonstrict Read Write Cache doesn’t employ any locks but can do inserts, update and deletes.
201
     */
202
    const CACHE_USAGE_NONSTRICT_READ_WRITE = 2;
203
204
    /**
205
     * Read Write Attempts to lock the entity before update/delete.
206
     */
207
    const CACHE_USAGE_READ_WRITE = 3;
208
209
    /**
210
     * READ-ONLY: The name of the entity class.
211
     *
212
     * @var string
213
     */
214
    public $name;
215
216
    /**
217
     * READ-ONLY: The namespace the entity class is contained in.
218
     *
219
     * @var string
220
     *
221
     * @todo Not really needed. Usage could be localized.
222
     */
223
    public $namespace;
224
225
    /**
226
     * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance
227
     * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same
228
     * as {@link $name}.
229
     *
230
     * @var string
231
     */
232
    public $rootEntityName;
233
234
    /**
235
     * READ-ONLY: The definition of custom generator. Only used for CUSTOM
236
     * generator type
237
     *
238
     * The definition has the following structure:
239
     * <code>
240
     * array(
241
     *     'class' => 'ClassName',
242
     * )
243
     * </code>
244
     *
245
     * @var array
246
     *
247
     * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
248
     */
249
    public $customGeneratorDefinition;
250
251
    /**
252
     * The name of the custom repository class used for the entity class.
253
     * (Optional).
254
     *
255
     * @var string
256
     */
257
    public $customRepositoryClassName;
258
259
    /**
260
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
261
     *
262
     * @var boolean
263
     */
264
    public $isMappedSuperclass = false;
265
266
    /**
267
     * READ-ONLY: Whether this class describes the mapping of an embeddable class.
268
     *
269
     * @var boolean
270
     */
271
    public $isEmbeddedClass = false;
272
273
    /**
274
     * READ-ONLY: The names of the parent classes (ancestors).
275
     *
276
     * @var array
277
     */
278
    public $parentClasses = [];
279
280
    /**
281
     * READ-ONLY: The names of all subclasses (descendants).
282
     *
283
     * @var array
284
     */
285
    public $subClasses = [];
286
287
    /**
288
     * READ-ONLY: The names of all embedded classes based on properties.
289
     *
290
     * @var array
291
     */
292
    public $embeddedClasses = [];
293
294
    /**
295
     * READ-ONLY: The named queries allowed to be called directly from Repository.
296
     *
297
     * @var array
298
     */
299
    public $namedQueries = [];
300
301
    /**
302
     * READ-ONLY: The named native queries allowed to be called directly from Repository.
303
     *
304
     * A native SQL named query definition has the following structure:
305
     * <pre>
306
     * array(
307
     *     'name'               => <query name>,
308
     *     'query'              => <sql query>,
309
     *     'resultClass'        => <class of the result>,
310
     *     'resultSetMapping'   => <name of a SqlResultSetMapping>
311
     * )
312
     * </pre>
313
     *
314
     * @var array
315
     */
316
    public $namedNativeQueries = [];
317
318
    /**
319
     * READ-ONLY: The mappings of the results of native SQL queries.
320
     *
321
     * A native result mapping definition has the following structure:
322
     * <pre>
323
     * array(
324
     *     'name'               => <result name>,
325
     *     'entities'           => array(<entity result mapping>),
326
     *     'columns'            => array(<column result mapping>)
327
     * )
328
     * </pre>
329
     *
330
     * @var array
331
     */
332
    public $sqlResultSetMappings = [];
333
334
    /**
335
     * READ-ONLY: The field names of all fields that are part of the identifier/primary key
336
     * of the mapped entity class.
337
     *
338
     * @var array
339
     */
340
    public $identifier = [];
341
342
    /**
343
     * READ-ONLY: The inheritance mapping type used by the class.
344
     *
345
     * @var integer
346
     */
347
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
348
349
    /**
350
     * READ-ONLY: The Id generator type used by the class.
351
     *
352
     * @var int
353
     */
354
    public $generatorType = self::GENERATOR_TYPE_NONE;
355
356
    /**
357
     * READ-ONLY: The field mappings of the class.
358
     * Keys are field names and values are mapping definitions.
359
     *
360
     * The mapping definition array has the following values:
361
     *
362
     * - <b>fieldName</b> (string)
363
     * The name of the field in the Entity.
364
     *
365
     * - <b>type</b> (string)
366
     * The type name of the mapped field. Can be one of Doctrine's mapping types
367
     * or a custom mapping type.
368
     *
369
     * - <b>columnName</b> (string, optional)
370
     * The column name. Optional. Defaults to the field name.
371
     *
372
     * - <b>length</b> (integer, optional)
373
     * The database length of the column. Optional. Default value taken from
374
     * the type.
375
     *
376
     * - <b>id</b> (boolean, optional)
377
     * Marks the field as the primary key of the entity. Multiple fields of an
378
     * entity can have the id attribute, forming a composite key.
379
     *
380
     * - <b>nullable</b> (boolean, optional)
381
     * Whether the column is nullable. Defaults to FALSE.
382
     *
383
     * - <b>columnDefinition</b> (string, optional, schema-only)
384
     * The SQL fragment that is used when generating the DDL for the column.
385
     *
386
     * - <b>precision</b> (integer, optional, schema-only)
387
     * The precision of a decimal column. Only valid if the column type is decimal.
388
     *
389
     * - <b>scale</b> (integer, optional, schema-only)
390
     * The scale of a decimal column. Only valid if the column type is decimal.
391
     *
392
     * - <b>'unique'</b> (string, optional, schema-only)
393
     * Whether a unique constraint should be generated for the column.
394
     *
395
     * @var array
396
     */
397
    public $fieldMappings = [];
398
399
    /**
400
     * READ-ONLY: An array of field names. Used to look up field names from column names.
401
     * Keys are column names and values are field names.
402
     *
403
     * @var array
404
     */
405
    public $fieldNames = [];
406
407
    /**
408
     * READ-ONLY: A map of field names to column names. Keys are field names and values column names.
409
     * Used to look up column names from field names.
410
     * This is the reverse lookup map of $_fieldNames.
411
     *
412
     * @var array
413
     *
414
     * @deprecated 3.0 Remove this.
415
     */
416
    public $columnNames = [];
417
418
    /**
419
     * READ-ONLY: The discriminator value of this class.
420
     *
421
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
422
     * where a discriminator column is used.</b>
423
     *
424
     * @var mixed
425
     *
426
     * @see discriminatorColumn
427
     */
428
    public $discriminatorValue;
429
430
    /**
431
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
432
     *
433
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
434
     * where a discriminator column is used.</b>
435
     *
436
     * @var mixed
437
     *
438
     * @see discriminatorColumn
439
     */
440
    public $discriminatorMap = [];
441
442
    /**
443
     * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
444
     * inheritance mappings.
445
     *
446
     * @var array
447
     */
448
    public $discriminatorColumn;
449
450
    /**
451
     * READ-ONLY: The primary table definition. The definition is an array with the
452
     * following entries:
453
     *
454
     * name => <tableName>
455
     * schema => <schemaName>
456
     * indexes => array
457
     * uniqueConstraints => array
458
     *
459
     * @var array
460
     */
461
    public $table;
462
463
    /**
464
     * READ-ONLY: The registered lifecycle callbacks for entities of this class.
465
     *
466
     * @var array[]
467
     */
468
    public $lifecycleCallbacks = [];
469
470
    /**
471
     * READ-ONLY: The registered entity listeners.
472
     *
473
     * @var array
474
     */
475
    public $entityListeners = [];
476
477
    /**
478
     * READ-ONLY: The association mappings of this class.
479
     *
480
     * The mapping definition array supports the following keys:
481
     *
482
     * - <b>fieldName</b> (string)
483
     * The name of the field in the entity the association is mapped to.
484
     *
485
     * - <b>targetEntity</b> (string)
486
     * The class name of the target entity. If it is fully-qualified it is used as is.
487
     * If it is a simple, unqualified class name the namespace is assumed to be the same
488
     * as the namespace of the source entity.
489
     *
490
     * - <b>mappedBy</b> (string, required for bidirectional associations)
491
     * The name of the field that completes the bidirectional association on the owning side.
492
     * This key must be specified on the inverse side of a bidirectional association.
493
     *
494
     * - <b>inversedBy</b> (string, required for bidirectional associations)
495
     * The name of the field that completes the bidirectional association on the inverse side.
496
     * This key must be specified on the owning side of a bidirectional association.
497
     *
498
     * - <b>cascade</b> (array, optional)
499
     * The names of persistence operations to cascade on the association. The set of possible
500
     * values are: "persist", "remove", "detach", "merge", "refresh", "all" (implies all others).
501
     *
502
     * - <b>orderBy</b> (array, one-to-many/many-to-many only)
503
     * A map of field names (of the target entity) to sorting directions (ASC/DESC).
504
     * Example: array('priority' => 'desc')
505
     *
506
     * - <b>fetch</b> (integer, optional)
507
     * The fetching strategy to use for the association, usually defaults to FETCH_LAZY.
508
     * Possible values are: ClassMetadata::FETCH_EAGER, ClassMetadata::FETCH_LAZY.
509
     *
510
     * - <b>joinTable</b> (array, optional, many-to-many only)
511
     * Specification of the join table and its join columns (foreign keys).
512
     * Only valid for many-to-many mappings. Note that one-to-many associations can be mapped
513
     * through a join table by simply mapping the association as many-to-many with a unique
514
     * constraint on the join table.
515
     *
516
     * - <b>indexBy</b> (string, optional, to-many only)
517
     * Specification of a field on target-entity that is used to index the collection by.
518
     * This field HAS to be either the primary key or a unique column. Otherwise the collection
519
     * does not contain all the entities that are actually related.
520
     *
521
     * A join table definition has the following structure:
522
     * <pre>
523
     * array(
524
     *     'name' => <join table name>,
525
     *      'joinColumns' => array(<join column mapping from join table to source table>),
526
     *      'inverseJoinColumns' => array(<join column mapping from join table to target table>)
527
     * )
528
     * </pre>
529
     *
530
     * @var array
531
     */
532
    public $associationMappings = [];
533
534
    /**
535
     * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite.
536
     *
537
     * @var boolean
538
     */
539
    public $isIdentifierComposite = false;
540
541
    /**
542
     * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association.
543
     *
544
     * This flag is necessary because some code blocks require special treatment of this cases.
545
     *
546
     * @var boolean
547
     */
548
    public $containsForeignIdentifier = false;
549
550
    /**
551
     * READ-ONLY: The ID generator used for generating IDs for this class.
552
     *
553
     * @var \Doctrine\ORM\Id\AbstractIdGenerator
554
     *
555
     * @todo Remove!
556
     */
557
    public $idGenerator;
558
559
    /**
560
     * READ-ONLY: The definition of the sequence generator of this class. Only used for the
561
     * SEQUENCE generation strategy.
562
     *
563
     * The definition has the following structure:
564
     * <code>
565
     * array(
566
     *     'sequenceName' => 'name',
567
     *     'allocationSize' => 20,
568
     *     'initialValue' => 1
569
     * )
570
     * </code>
571
     *
572
     * @var array
573
     *
574
     * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
575
     */
576
    public $sequenceGeneratorDefinition;
577
578
    /**
579
     * READ-ONLY: The definition of the table generator of this class. Only used for the
580
     * TABLE generation strategy.
581
     *
582
     * @var array
583
     *
584
     * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
585
     */
586
    public $tableGeneratorDefinition;
587
588
    /**
589
     * READ-ONLY: The policy used for change-tracking on entities of this class.
590
     *
591
     * @var integer
592
     */
593
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
594
595
    /**
596
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
597
     * with optimistic locking.
598
     *
599
     * @var boolean
600
     */
601
    public $isVersioned;
602
603
    /**
604
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
605
     *
606
     * @var mixed
607
     */
608
    public $versionField;
609
610
    /**
611
     * @var array
612
     */
613
    public $cache = null;
614
615
    /**
616
     * The ReflectionClass instance of the mapped class.
617
     *
618
     * @var ReflectionClass
619
     */
620
    public $reflClass;
621
622
    /**
623
     * Is this entity marked as "read-only"?
624
     *
625
     * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance
626
     * optimization for entities that are immutable, either in your domain or through the relation database
627
     * (coming from a view, or a history table for example).
628
     *
629
     * @var bool
630
     */
631
    public $isReadOnly = false;
632
633
    /**
634
     * NamingStrategy determining the default column and table names.
635
     *
636
     * @var \Doctrine\ORM\Mapping\NamingStrategy
637
     */
638
    protected $namingStrategy;
639
640
    /**
641
     * The ReflectionProperty instances of the mapped class.
642
     *
643
     * @var \ReflectionProperty[]
644
     */
645
    public $reflFields = [];
646
647
    /**
648
     * @var \Doctrine\Instantiator\InstantiatorInterface|null
649
     */
650
    private $instantiator;
651
652
    /**
653
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
654
     * metadata of the class with the given name.
655
     *
656
     * @param string              $entityName     The name of the entity class the new instance is used for.
657
     * @param NamingStrategy|null $namingStrategy
658
     */
659
    public function __construct($entityName, NamingStrategy $namingStrategy = null)
660
    {
661
        $this->name = $entityName;
662
        $this->rootEntityName = $entityName;
663
        $this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy();
664
        $this->instantiator   = new Instantiator();
665
    }
666
667
    /**
668
     * Gets the ReflectionProperties of the mapped class.
669
     *
670
     * @return array An array of ReflectionProperty instances.
671
     */
672
    public function getReflectionProperties()
673
    {
674
        return $this->reflFields;
675
    }
676
677
    /**
678
     * Gets a ReflectionProperty for a specific field of the mapped class.
679
     *
680
     * @param string $name
681
     *
682
     * @return \ReflectionProperty
683
     */
684
    public function getReflectionProperty($name)
685
    {
686
        return $this->reflFields[$name];
687
    }
688
689
    /**
690
     * Gets the ReflectionProperty for the single identifier field.
691
     *
692
     * @return \ReflectionProperty
693
     *
694
     * @throws BadMethodCallException If the class has a composite identifier.
695
     */
696
    public function getSingleIdReflectionProperty()
697
    {
698
        if ($this->isIdentifierComposite) {
699
            throw new BadMethodCallException("Class " . $this->name . " has a composite identifier.");
700
        }
701
702
        return $this->reflFields[$this->identifier[0]];
703
    }
704
705
    /**
706
     * Extracts the identifier values of an entity of this class.
707
     *
708
     * For composite identifiers, the identifier values are returned as an array
709
     * with the same order as the field order in {@link identifier}.
710
     *
711
     * @param object $entity
712
     *
713
     * @return array
714
     */
715
    public function getIdentifierValues($entity)
716
    {
717
        if ($this->isIdentifierComposite) {
718
            $id = [];
719
720
            foreach ($this->identifier as $idField) {
721
                $value = $this->reflFields[$idField]->getValue($entity);
722
723
                if (null !== $value) {
724
                    $id[$idField] = $value;
725
                }
726
            }
727
728
            return $id;
729
        }
730
731
        $id = $this->identifier[0];
732
        $value = $this->reflFields[$id]->getValue($entity);
733
734
        if (null === $value) {
735
            return [];
736
        }
737
738
        return [$id => $value];
739
    }
740
741
    /**
742
     * Populates the entity identifier of an entity.
743
     *
744
     * @param object $entity
745
     * @param array  $id
746
     *
747
     * @return void
748
     *
749
     * @todo Rename to assignIdentifier()
750
     */
751
    public function setIdentifierValues($entity, array $id)
752
    {
753
        foreach ($id as $idField => $idValue) {
754
            $this->reflFields[$idField]->setValue($entity, $idValue);
755
        }
756
    }
757
758
    /**
759
     * Sets the specified field to the specified value on the given entity.
760
     *
761
     * @param object $entity
762
     * @param string $field
763
     * @param mixed  $value
764
     *
765
     * @return void
766
     */
767
    public function setFieldValue($entity, $field, $value)
768
    {
769
        $this->reflFields[$field]->setValue($entity, $value);
770
    }
771
772
    /**
773
     * Gets the specified field's value off the given entity.
774
     *
775
     * @param object $entity
776
     * @param string $field
777
     *
778
     * @return mixed
779
     */
780
    public function getFieldValue($entity, $field)
781
    {
782
        return $this->reflFields[$field]->getValue($entity);
783
    }
784
785
    /**
786
     * Creates a string representation of this instance.
787
     *
788
     * @return string The string representation of this instance.
789
     *
790
     * @todo Construct meaningful string representation.
791
     */
792
    public function __toString()
793
    {
794
        return __CLASS__ . '@' . spl_object_hash($this);
795
    }
796
797
    /**
798
     * Determines which fields get serialized.
799
     *
800
     * It is only serialized what is necessary for best unserialization performance.
801
     * That means any metadata properties that are not set or empty or simply have
802
     * their default value are NOT serialized.
803
     *
804
     * Parts that are also NOT serialized because they can not be properly unserialized:
805
     *      - reflClass (ReflectionClass)
806
     *      - reflFields (ReflectionProperty array)
807
     *
808
     * @return array The names of all the fields that should be serialized.
809
     */
810
    public function __sleep()
811
    {
812
        // This metadata is always serialized/cached.
813
        $serialized = [
814
            'associationMappings',
815
            'columnNames', //TODO: 3.0 Remove this. Can use fieldMappings[$fieldName]['columnName']
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% 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...
816
            'fieldMappings',
817
            'fieldNames',
818
            'embeddedClasses',
819
            'identifier',
820
            'isIdentifierComposite', // TODO: REMOVE
821
            'name',
822
            'namespace', // TODO: REMOVE
823
            'table',
824
            'rootEntityName',
825
            'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime.
826
        ];
827
828
        // The rest of the metadata is only serialized if necessary.
829
        if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) {
830
            $serialized[] = 'changeTrackingPolicy';
831
        }
832
833
        if ($this->customRepositoryClassName) {
834
            $serialized[] = 'customRepositoryClassName';
835
        }
836
837
        if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE) {
838
            $serialized[] = 'inheritanceType';
839
            $serialized[] = 'discriminatorColumn';
840
            $serialized[] = 'discriminatorValue';
841
            $serialized[] = 'discriminatorMap';
842
            $serialized[] = 'parentClasses';
843
            $serialized[] = 'subClasses';
844
        }
845
846
        if ($this->generatorType != self::GENERATOR_TYPE_NONE) {
847
            $serialized[] = 'generatorType';
848
            if ($this->generatorType == self::GENERATOR_TYPE_SEQUENCE) {
849
                $serialized[] = 'sequenceGeneratorDefinition';
850
            }
851
        }
852
853
        if ($this->isMappedSuperclass) {
854
            $serialized[] = 'isMappedSuperclass';
855
        }
856
857
        if ($this->isEmbeddedClass) {
858
            $serialized[] = 'isEmbeddedClass';
859
        }
860
861
        if ($this->containsForeignIdentifier) {
862
            $serialized[] = 'containsForeignIdentifier';
863
        }
864
865
        if ($this->isVersioned) {
866
            $serialized[] = 'isVersioned';
867
            $serialized[] = 'versionField';
868
        }
869
870
        if ($this->lifecycleCallbacks) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lifecycleCallbacks of type array<mixed,array> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
871
            $serialized[] = 'lifecycleCallbacks';
872
        }
873
874
        if ($this->entityListeners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityListeners of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
875
            $serialized[] = 'entityListeners';
876
        }
877
878
        if ($this->namedQueries) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->namedQueries of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
879
            $serialized[] = 'namedQueries';
880
        }
881
882
        if ($this->namedNativeQueries) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->namedNativeQueries of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
883
            $serialized[] = 'namedNativeQueries';
884
        }
885
886
        if ($this->sqlResultSetMappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sqlResultSetMappings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
887
            $serialized[] = 'sqlResultSetMappings';
888
        }
889
890
        if ($this->isReadOnly) {
891
            $serialized[] = 'isReadOnly';
892
        }
893
894
        if ($this->customGeneratorDefinition) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customGeneratorDefinition of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
895
            $serialized[] = "customGeneratorDefinition";
896
        }
897
898
        if ($this->cache) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cache of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
899
            $serialized[] = 'cache';
900
        }
901
902
        return $serialized;
903
    }
904
905
    /**
906
     * Creates a new instance of the mapped class, without invoking the constructor.
907
     *
908
     * @return object
909
     */
910
    public function newInstance()
911
    {
912
        return $this->instantiator->instantiate($this->name);
0 ignored issues
show
Bug introduced by
The method instantiate() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

912
        return $this->instantiator->/** @scrutinizer ignore-call */ instantiate($this->name);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
913
    }
914
915
    /**
916
     * Restores some state that can not be serialized/unserialized.
917
     *
918
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService
919
     *
920
     * @return void
921
     */
922
    public function wakeupReflection($reflService)
923
    {
924
        // Restore ReflectionClass and properties
925
        $this->reflClass    = $reflService->getClass($this->name);
926
        $this->instantiator = $this->instantiator ?: new Instantiator();
927
928
        $parentReflFields = [];
929
930
        foreach ($this->embeddedClasses as $property => $embeddedClass) {
931
            if (isset($embeddedClass['declaredField'])) {
932
                $parentReflFields[$property] = new ReflectionEmbeddedProperty(
933
                    $parentReflFields[$embeddedClass['declaredField']],
934
                    $reflService->getAccessibleProperty(
935
                        $this->embeddedClasses[$embeddedClass['declaredField']]['class'],
936
                        $embeddedClass['originalField']
937
                    ),
938
                    $this->embeddedClasses[$embeddedClass['declaredField']]['class']
939
                );
940
941
                continue;
942
            }
943
944
            $fieldRefl = $reflService->getAccessibleProperty(
945
                $embeddedClass['declared'] ?? $this->name,
946
                $property
947
            );
948
949
            $parentReflFields[$property] = $fieldRefl;
950
            $this->reflFields[$property] = $fieldRefl;
951
        }
952
953
        foreach ($this->fieldMappings as $field => $mapping) {
954
            if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
955
                $this->reflFields[$field] = new ReflectionEmbeddedProperty(
956
                    $parentReflFields[$mapping['declaredField']],
957
                    $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
958
                    $mapping['originalClass']
959
                );
960
                continue;
961
            }
962
963
            $this->reflFields[$field] = isset($mapping['declared'])
964
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
965
                : $reflService->getAccessibleProperty($this->name, $field);
966
        }
967
968
        foreach ($this->associationMappings as $field => $mapping) {
969
            $this->reflFields[$field] = isset($mapping['declared'])
970
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
971
                : $reflService->getAccessibleProperty($this->name, $field);
972
        }
973
    }
974
975
    /**
976
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
977
     * metadata of the class with the given name.
978
     *
979
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService The reflection service.
980
     *
981
     * @return void
982
     */
983
    public function initializeReflection($reflService)
984
    {
985
        $this->reflClass = $reflService->getClass($this->name);
986
        $this->namespace = $reflService->getClassNamespace($this->name);
987
988
        if ($this->reflClass) {
989
            $this->name = $this->rootEntityName = $this->reflClass->getName();
990
        }
991
992
        $this->table['name'] = $this->namingStrategy->classToTableName($this->name);
993
    }
994
995
    /**
996
     * Validates Identifier.
997
     *
998
     * @return void
999
     *
1000
     * @throws MappingException
1001
     */
1002
    public function validateIdentifier()
1003
    {
1004
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
1005
            return;
1006
        }
1007
1008
        // Verify & complete identifier mapping
1009
        if ( ! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1010
            throw MappingException::identifierRequired($this->name);
1011
        }
1012
1013
        if ($this->usesIdGenerator() && $this->isIdentifierComposite) {
1014
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name);
1015
        }
1016
    }
1017
1018
    /**
1019
     * Validates association targets actually exist.
1020
     *
1021
     * @return void
1022
     *
1023
     * @throws MappingException
1024
     */
1025
    public function validateAssociations()
1026
    {
1027
        foreach ($this->associationMappings as $mapping) {
1028
            if ( ! ClassLoader::classExists($mapping['targetEntity']) ) {
1029
                throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']);
1030
            }
1031
        }
1032
    }
1033
1034
    /**
1035
     * Validates lifecycle callbacks.
1036
     *
1037
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService
1038
     *
1039
     * @return void
1040
     *
1041
     * @throws MappingException
1042
     */
1043
    public function validateLifecycleCallbacks($reflService)
1044
    {
1045
        foreach ($this->lifecycleCallbacks as $callbacks) {
1046
            foreach ($callbacks as $callbackFuncName) {
1047
                if ( ! $reflService->hasPublicMethod($this->name, $callbackFuncName)) {
1048
                    throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName);
1049
                }
1050
            }
1051
        }
1052
    }
1053
1054
    /**
1055
     * {@inheritDoc}
1056
     */
1057
    public function getReflectionClass()
1058
    {
1059
        return $this->reflClass;
1060
    }
1061
1062
    /**
1063
     * @param array $cache
1064
     *
1065
     * @return void
1066
     */
1067
    public function enableCache(array $cache)
1068
    {
1069
        if ( ! isset($cache['usage'])) {
1070
            $cache['usage'] = self::CACHE_USAGE_READ_ONLY;
1071
        }
1072
1073 View Code Duplication
        if ( ! isset($cache['region'])) {
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...
1074
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName));
1075
        }
1076
1077
        $this->cache = $cache;
1078
    }
1079
1080
    /**
1081
     * @param string $fieldName
1082
     * @param array  $cache
1083
     *
1084
     * @return void
1085
     */
1086
    public function enableAssociationCache($fieldName, array $cache)
1087
    {
1088
        $this->associationMappings[$fieldName]['cache'] = $this->getAssociationCacheDefaults($fieldName, $cache);
1089
    }
1090
1091
    /**
1092
     * @param string $fieldName
1093
     * @param array  $cache
1094
     *
1095
     * @return array
1096
     */
1097
    public function getAssociationCacheDefaults($fieldName, array $cache)
1098
    {
1099
        if ( ! isset($cache['usage'])) {
1100
            $cache['usage'] = isset($this->cache['usage'])
1101
                ? $this->cache['usage']
1102
                : self::CACHE_USAGE_READ_ONLY;
1103
        }
1104
1105 View Code Duplication
        if ( ! isset($cache['region'])) {
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...
1106
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
1107
        }
1108
1109
        return $cache;
1110
    }
1111
1112
    /**
1113
     * Sets the change tracking policy used by this class.
1114
     *
1115
     * @param integer $policy
1116
     *
1117
     * @return void
1118
     */
1119
    public function setChangeTrackingPolicy($policy)
1120
    {
1121
        $this->changeTrackingPolicy = $policy;
1122
    }
1123
1124
    /**
1125
     * Whether the change tracking policy of this class is "deferred explicit".
1126
     *
1127
     * @return boolean
1128
     */
1129
    public function isChangeTrackingDeferredExplicit()
1130
    {
1131
        return self::CHANGETRACKING_DEFERRED_EXPLICIT === $this->changeTrackingPolicy;
1132
    }
1133
1134
    /**
1135
     * Whether the change tracking policy of this class is "deferred implicit".
1136
     *
1137
     * @return boolean
1138
     */
1139
    public function isChangeTrackingDeferredImplicit()
1140
    {
1141
        return self::CHANGETRACKING_DEFERRED_IMPLICIT === $this->changeTrackingPolicy;
1142
    }
1143
1144
    /**
1145
     * Whether the change tracking policy of this class is "notify".
1146
     *
1147
     * @return boolean
1148
     */
1149
    public function isChangeTrackingNotify()
1150
    {
1151
        return self::CHANGETRACKING_NOTIFY === $this->changeTrackingPolicy;
1152
    }
1153
1154
    /**
1155
     * Checks whether a field is part of the identifier/primary key field(s).
1156
     *
1157
     * @param string $fieldName The field name.
1158
     *
1159
     * @return boolean TRUE if the field is part of the table identifier/primary key field(s),
1160
     *                 FALSE otherwise.
1161
     */
1162
    public function isIdentifier($fieldName)
1163
    {
1164
        if ( ! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1165
            return false;
1166
        }
1167
1168
        if ( ! $this->isIdentifierComposite) {
1169
            return $fieldName === $this->identifier[0];
1170
        }
1171
1172
        return in_array($fieldName, $this->identifier, true);
1173
    }
1174
1175
    /**
1176
     * Checks if the field is unique.
1177
     *
1178
     * @param string $fieldName The field name.
1179
     *
1180
     * @return boolean TRUE if the field is unique, FALSE otherwise.
1181
     */
1182
    public function isUniqueField($fieldName)
1183
    {
1184
        $mapping = $this->getFieldMapping($fieldName);
1185
1186
        return false !== $mapping && isset($mapping['unique']) && $mapping['unique'];
1187
    }
1188
1189
    /**
1190
     * Checks if the field is not null.
1191
     *
1192
     * @param string $fieldName The field name.
1193
     *
1194
     * @return boolean TRUE if the field is not null, FALSE otherwise.
1195
     */
1196
    public function isNullable($fieldName)
1197
    {
1198
        $mapping = $this->getFieldMapping($fieldName);
1199
1200
        return false !== $mapping && isset($mapping['nullable']) && $mapping['nullable'];
1201
    }
1202
1203
    /**
1204
     * Gets a column name for a field name.
1205
     * If the column name for the field cannot be found, the given field name
1206
     * is returned.
1207
     *
1208
     * @param string $fieldName The field name.
1209
     *
1210
     * @return string The column name.
1211
     */
1212
    public function getColumnName($fieldName)
1213
    {
1214
        return isset($this->columnNames[$fieldName])
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\ClassMetadata::$columnNames has been deprecated: 3.0 Remove this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1214
        return isset(/** @scrutinizer ignore-deprecated */ $this->columnNames[$fieldName])

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1215
            ? $this->columnNames[$fieldName]
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\ClassMetadata::$columnNames has been deprecated: 3.0 Remove this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1215
            ? /** @scrutinizer ignore-deprecated */ $this->columnNames[$fieldName]

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1216
            : $fieldName;
1217
    }
1218
1219
    /**
1220
     * Gets the mapping of a (regular) field that holds some data but not a
1221
     * reference to another object.
1222
     *
1223
     * @param string $fieldName The field name.
1224
     *
1225
     * @return array The field mapping.
1226
     *
1227
     * @throws MappingException
1228
     */
1229 View Code Duplication
    public function getFieldMapping($fieldName)
1230
    {
1231
        if ( ! isset($this->fieldMappings[$fieldName])) {
1232
            throw MappingException::mappingNotFound($this->name, $fieldName);
1233
        }
1234
1235
        return $this->fieldMappings[$fieldName];
1236
    }
1237
1238
    /**
1239
     * Gets the mapping of an association.
1240
     *
1241
     * @see ClassMetadata::$associationMappings
1242
     *
1243
     * @param string $fieldName The field name that represents the association in
1244
     *                          the object model.
1245
     *
1246
     * @return array The mapping.
1247
     *
1248
     * @throws MappingException
1249
     */
1250 View Code Duplication
    public function getAssociationMapping($fieldName)
1251
    {
1252
        if ( ! isset($this->associationMappings[$fieldName])) {
1253
            throw MappingException::mappingNotFound($this->name, $fieldName);
1254
        }
1255
1256
        return $this->associationMappings[$fieldName];
1257
    }
1258
1259
    /**
1260
     * Gets all association mappings of the class.
1261
     *
1262
     * @return array
1263
     */
1264
    public function getAssociationMappings()
1265
    {
1266
        return $this->associationMappings;
1267
    }
1268
1269
    /**
1270
     * Gets the field name for a column name.
1271
     * If no field name can be found the column name is returned.
1272
     *
1273
     * @param string $columnName The column name.
1274
     *
1275
     * @return string The column alias.
1276
     */
1277
    public function getFieldName($columnName)
1278
    {
1279
        return isset($this->fieldNames[$columnName])
1280
            ? $this->fieldNames[$columnName]
1281
            : $columnName;
1282
    }
1283
1284
    /**
1285
     * Gets the named query.
1286
     *
1287
     * @see ClassMetadata::$namedQueries
1288
     *
1289
     * @param string $queryName The query name.
1290
     *
1291
     * @return string
1292
     *
1293
     * @throws MappingException
1294
     */
1295 View Code Duplication
    public function getNamedQuery($queryName)
1296
    {
1297
        if ( ! isset($this->namedQueries[$queryName])) {
1298
            throw MappingException::queryNotFound($this->name, $queryName);
1299
        }
1300
1301
        return $this->namedQueries[$queryName]['dql'];
1302
    }
1303
1304
    /**
1305
     * Gets all named queries of the class.
1306
     *
1307
     * @return array
1308
     */
1309
    public function getNamedQueries()
1310
    {
1311
        return $this->namedQueries;
1312
    }
1313
1314
    /**
1315
     * Gets the named native query.
1316
     *
1317
     * @see ClassMetadata::$namedNativeQueries
1318
     *
1319
     * @param string $queryName The query name.
1320
     *
1321
     * @return array
1322
     *
1323
     * @throws MappingException
1324
     */
1325 View Code Duplication
    public function getNamedNativeQuery($queryName)
1326
    {
1327
        if ( ! isset($this->namedNativeQueries[$queryName])) {
1328
            throw MappingException::queryNotFound($this->name, $queryName);
1329
        }
1330
1331
        return $this->namedNativeQueries[$queryName];
1332
    }
1333
1334
    /**
1335
     * Gets all named native queries of the class.
1336
     *
1337
     * @return array
1338
     */
1339
    public function getNamedNativeQueries()
1340
    {
1341
        return $this->namedNativeQueries;
1342
    }
1343
1344
    /**
1345
     * Gets the result set mapping.
1346
     *
1347
     * @see ClassMetadata::$sqlResultSetMappings
1348
     *
1349
     * @param string $name The result set mapping name.
1350
     *
1351
     * @return array
1352
     *
1353
     * @throws MappingException
1354
     */
1355
    public function getSqlResultSetMapping($name)
1356
    {
1357
        if ( ! isset($this->sqlResultSetMappings[$name])) {
1358
            throw MappingException::resultMappingNotFound($this->name, $name);
1359
        }
1360
1361
        return $this->sqlResultSetMappings[$name];
1362
    }
1363
1364
    /**
1365
     * Gets all sql result set mappings of the class.
1366
     *
1367
     * @return array
1368
     */
1369
    public function getSqlResultSetMappings()
1370
    {
1371
        return $this->sqlResultSetMappings;
1372
    }
1373
1374
    /**
1375
     * Validates & completes the given field mapping.
1376
     *
1377
     * @param array $mapping The field mapping to validate & complete.
1378
     *
1379
     * @return void
1380
     *
1381
     * @throws MappingException
1382
     */
1383
    protected function _validateAndCompleteFieldMapping(array &$mapping)
1384
    {
1385
        // Check mandatory fields
1386 View Code Duplication
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
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...
1387
            throw MappingException::missingFieldName($this->name);
1388
        }
1389
1390
        if ( ! isset($mapping['type'])) {
1391
            // Default to string
1392
            $mapping['type'] = 'string';
1393
        }
1394
1395
        // Complete fieldName and columnName mapping
1396
        if ( ! isset($mapping['columnName'])) {
1397
            $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name);
1398
        }
1399
1400
        if ('`' === $mapping['columnName'][0]) {
1401
            $mapping['columnName']  = trim($mapping['columnName'], '`');
1402
            $mapping['quoted']      = true;
1403
        }
1404
1405
        $this->columnNames[$mapping['fieldName']] = $mapping['columnName'];
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\ClassMetadata::$columnNames has been deprecated: 3.0 Remove this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1405
        /** @scrutinizer ignore-deprecated */ $this->columnNames[$mapping['fieldName']] = $mapping['columnName'];

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1406
1407
        if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorColumn && $this->discriminatorColumn['name'] === $mapping['columnName'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->discriminatorColumn of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1408
            throw MappingException::duplicateColumnName($this->name, $mapping['columnName']);
1409
        }
1410
1411
        $this->fieldNames[$mapping['columnName']] = $mapping['fieldName'];
1412
1413
        // Complete id mapping
1414
        if (isset($mapping['id']) && true === $mapping['id']) {
1415
            if ($this->versionField == $mapping['fieldName']) {
1416
                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
1417
            }
1418
1419
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1420
                $this->identifier[] = $mapping['fieldName'];
1421
            }
1422
1423
            // Check for composite key
1424 View Code Duplication
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
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...
1425
                $this->isIdentifierComposite = true;
1426
            }
1427
        }
1428
1429
        if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
1430 View Code Duplication
            if (isset($mapping['id']) && true === $mapping['id']) {
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...
1431
                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
1432
            }
1433
1434
            $mapping['requireSQLConversion'] = true;
1435
        }
1436
    }
1437
1438
    /**
1439
     * Validates & completes the basic mapping information that is common to all
1440
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
1441
     *
1442
     * @param array $mapping The mapping.
1443
     *
1444
     * @return array The updated mapping.
1445
     *
1446
     * @throws MappingException If something is wrong with the mapping.
1447
     */
1448
    protected function _validateAndCompleteAssociationMapping(array $mapping)
1449
    {
1450
        if ( ! isset($mapping['mappedBy'])) {
1451
            $mapping['mappedBy'] = null;
1452
        }
1453
1454
        if ( ! isset($mapping['inversedBy'])) {
1455
            $mapping['inversedBy'] = null;
1456
        }
1457
1458
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
1459
1460
        if (empty($mapping['indexBy'])) {
1461
            unset($mapping['indexBy']);
1462
        }
1463
1464
        // If targetEntity is unqualified, assume it is in the same namespace as
1465
        // the sourceEntity.
1466
        $mapping['sourceEntity'] = $this->name;
1467
1468
        if (isset($mapping['targetEntity'])) {
1469
            $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
0 ignored issues
show
Bug introduced by
It seems like $mapping['targetEntity'] can also be of type true; however, parameter $className of Doctrine\ORM\Mapping\Cla...llyQualifiedClassName() does only seem to accept null|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1469
            $mapping['targetEntity'] = $this->fullyQualifiedClassName(/** @scrutinizer ignore-type */ $mapping['targetEntity']);
Loading history...
1470
            $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
1471
        }
1472
1473 View Code Duplication
        if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
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...
1474
            throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
1475
        }
1476
1477
        // Complete id mapping
1478
        if (isset($mapping['id']) && true === $mapping['id']) {
1479
            if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1480
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
1481
            }
1482
1483
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1484
                if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) {
1485
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
1486
                        $mapping['targetEntity'], $this->name, $mapping['fieldName']
1487
                    );
1488
                }
1489
1490
                $this->identifier[] = $mapping['fieldName'];
1491
                $this->containsForeignIdentifier = true;
1492
            }
1493
1494
            // Check for composite key
1495 View Code Duplication
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
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...
1496
                $this->isIdentifierComposite = true;
1497
            }
1498
1499
            if ($this->cache && !isset($mapping['cache'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cache of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1500
                throw CacheException::nonCacheableEntityAssociation($this->name, $mapping['fieldName']);
1501
            }
1502
        }
1503
1504
        // Mandatory attributes for both sides
1505
        // Mandatory: fieldName, targetEntity
1506 View Code Duplication
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
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...
1507
            throw MappingException::missingFieldName($this->name);
1508
        }
1509
1510
        if ( ! isset($mapping['targetEntity'])) {
1511
            throw MappingException::missingTargetEntity($mapping['fieldName']);
1512
        }
1513
1514
        // Mandatory and optional attributes for either side
1515
        if ( ! $mapping['mappedBy']) {
1516
            if (isset($mapping['joinTable']) && $mapping['joinTable']) {
1517
                if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
1518
                    $mapping['joinTable']['name']   = trim($mapping['joinTable']['name'], '`');
1519
                    $mapping['joinTable']['quoted'] = true;
1520
                }
1521
            }
1522
        } else {
1523
            $mapping['isOwningSide'] = false;
1524
        }
1525
1526 View Code Duplication
        if (isset($mapping['id']) && true === $mapping['id'] && $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...
1527
            throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']);
1528
        }
1529
1530
        // Fetch mode. Default fetch mode to LAZY, if not set.
1531
        if ( ! isset($mapping['fetch'])) {
1532
            $mapping['fetch'] = self::FETCH_LAZY;
1533
        }
1534
1535
        // Cascades
1536
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
1537
1538
        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1539
        if (in_array('all', $cascades)) {
1540
            $cascades = $allCascades;
1541
        } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
1542
            throw MappingException::invalidCascadeOption(
1543
                array_diff($cascades, $allCascades),
1544
                $this->name,
1545
                $mapping['fieldName']
1546
            );
1547
        }
1548
1549
        $mapping['cascade'] = $cascades;
1550
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1551
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1552
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1553
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1554
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1555
1556
        return $mapping;
1557
    }
1558
1559
    /**
1560
     * Validates & completes a one-to-one association mapping.
1561
     *
1562
     * @param array $mapping The mapping to validate & complete.
1563
     *
1564
     * @return array The validated & completed mapping.
1565
     *
1566
     * @throws RuntimeException
1567
     * @throws MappingException
1568
     */
1569
    protected function _validateAndCompleteOneToOneMapping(array $mapping)
1570
    {
1571
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1572
1573
        if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
1574
            $mapping['isOwningSide'] = true;
1575
        }
1576
1577
        if ($mapping['isOwningSide']) {
1578
            if (empty($mapping['joinColumns'])) {
1579
                // Apply default join column
1580
                $mapping['joinColumns'] = [
1581
                    [
1582
                        'name' => $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name),
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\ORM\Mapping\Nam...ategy::joinColumnName() has too many arguments starting with $this->name. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1582
                        'name' => $this->namingStrategy->/** @scrutinizer ignore-call */ joinColumnName($mapping['fieldName'], $this->name),

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1583
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName()
1584
                    ]
1585
                ];
1586
            }
1587
1588
            $uniqueConstraintColumns = [];
1589
1590
            foreach ($mapping['joinColumns'] as &$joinColumn) {
1591
                if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
1592
                    if (count($mapping['joinColumns']) === 1) {
1593
                        if (empty($mapping['id'])) {
1594
                            $joinColumn['unique'] = true;
1595
                        }
1596
                    } else {
1597
                        $uniqueConstraintColumns[] = $joinColumn['name'];
1598
                    }
1599
                }
1600
1601
                if (empty($joinColumn['name'])) {
1602
                    $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name);
1603
                }
1604
1605
                if (empty($joinColumn['referencedColumnName'])) {
1606
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1607
                }
1608
1609
                if ($joinColumn['name'][0] === '`') {
1610
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1611
                    $joinColumn['quoted'] = true;
1612
                }
1613
1614
                if ($joinColumn['referencedColumnName'][0] === '`') {
1615
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1616
                    $joinColumn['quoted']               = true;
1617
                }
1618
1619
                $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1620
                $mapping['joinColumnFieldNames'][$joinColumn['name']] = $joinColumn['fieldName'] ?? $joinColumn['name'];
1621
            }
1622
1623
            if ($uniqueConstraintColumns) {
1624
                if ( ! $this->table) {
1625
                    throw new RuntimeException("ClassMetadata::setTable() has to be called before defining a one to one relationship.");
1626
                }
1627
1628
                $this->table['uniqueConstraints'][$mapping['fieldName'] . "_uniq"] = [
1629
                    'columns' => $uniqueConstraintColumns
1630
                ];
1631
            }
1632
1633
            $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
1634
        }
1635
1636
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1637
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1638
1639
        if ($mapping['orphanRemoval']) {
1640
            unset($mapping['unique']);
1641
        }
1642
1643 View Code Duplication
        if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
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...
1644
            throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
1645
        }
1646
1647
        return $mapping;
1648
    }
1649
1650
    /**
1651
     * Validates & completes a one-to-many association mapping.
1652
     *
1653
     * @param array $mapping The mapping to validate and complete.
1654
     *
1655
     * @return array The validated and completed mapping.
1656
     *
1657
     * @throws MappingException
1658
     * @throws InvalidArgumentException
1659
     */
1660
    protected function _validateAndCompleteOneToManyMapping(array $mapping)
1661
    {
1662
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1663
1664
        // OneToMany-side MUST be inverse (must have mappedBy)
1665
        if ( ! isset($mapping['mappedBy'])) {
1666
            throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
1667
        }
1668
1669
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1670
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1671
1672
        $this->assertMappingOrderBy($mapping);
1673
1674
        return $mapping;
1675
    }
1676
1677
    /**
1678
     * Validates & completes a many-to-many association mapping.
1679
     *
1680
     * @param array $mapping The mapping to validate & complete.
1681
     *
1682
     * @return array The validated & completed mapping.
1683
     *
1684
     * @throws \InvalidArgumentException
1685
     */
1686
    protected function _validateAndCompleteManyToManyMapping(array $mapping)
1687
    {
1688
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1689
1690
        if ($mapping['isOwningSide']) {
1691
            // owning side MUST have a join table
1692
            if ( ! isset($mapping['joinTable']['name'])) {
1693
                $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
1694
            }
1695
1696
            $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
1697
                && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
1698
1699 View Code Duplication
            if ( ! isset($mapping['joinTable']['joinColumns'])) {
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...
1700
                $mapping['joinTable']['joinColumns'] = [
1701
                    [
1702
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
1703
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1704
                        'onDelete' => 'CASCADE'
1705
                    ]
1706
                ];
1707
            }
1708
1709 View Code Duplication
            if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
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...
1710
                $mapping['joinTable']['inverseJoinColumns'] = [
1711
                    [
1712
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
1713
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1714
                        'onDelete' => 'CASCADE'
1715
                    ]
1716
                ];
1717
            }
1718
1719
            $mapping['joinTableColumns'] = [];
1720
1721 View Code Duplication
            foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
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...
1722
                if (empty($joinColumn['name'])) {
1723
                    $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
1724
                }
1725
1726
                if (empty($joinColumn['referencedColumnName'])) {
1727
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1728
                }
1729
1730
                if ($joinColumn['name'][0] === '`') {
1731
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1732
                    $joinColumn['quoted'] = true;
1733
                }
1734
1735
                if ($joinColumn['referencedColumnName'][0] === '`') {
1736
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1737
                    $joinColumn['quoted']               = true;
1738
                }
1739
1740
                if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') {
1741
                    $mapping['isOnDeleteCascade'] = true;
1742
                }
1743
1744
                $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1745
                $mapping['joinTableColumns'][] = $joinColumn['name'];
1746
            }
1747
1748 View Code Duplication
            foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) {
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...
1749
                if (empty($inverseJoinColumn['name'])) {
1750
                    $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']);
1751
                }
1752
1753
                if (empty($inverseJoinColumn['referencedColumnName'])) {
1754
                    $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1755
                }
1756
1757
                if ($inverseJoinColumn['name'][0] === '`') {
1758
                    $inverseJoinColumn['name']   = trim($inverseJoinColumn['name'], '`');
1759
                    $inverseJoinColumn['quoted'] = true;
1760
                }
1761
1762
                if ($inverseJoinColumn['referencedColumnName'][0] === '`') {
1763
                    $inverseJoinColumn['referencedColumnName']  = trim($inverseJoinColumn['referencedColumnName'], '`');
1764
                    $inverseJoinColumn['quoted']                = true;
1765
                }
1766
1767
                if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
1768
                    $mapping['isOnDeleteCascade'] = true;
1769
                }
1770
1771
                $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
1772
                $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
1773
            }
1774
        }
1775
1776
        $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1777
1778
        $this->assertMappingOrderBy($mapping);
1779
1780
        return $mapping;
1781
    }
1782
1783
    /**
1784
     * {@inheritDoc}
1785
     */
1786
    public function getIdentifierFieldNames()
1787
    {
1788
        return $this->identifier;
1789
    }
1790
1791
    /**
1792
     * Gets the name of the single id field. Note that this only works on
1793
     * entity classes that have a single-field pk.
1794
     *
1795
     * @return string
1796
     *
1797
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1798
     */
1799
    public function getSingleIdentifierFieldName()
1800
    {
1801
        if ($this->isIdentifierComposite) {
1802
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
1803
        }
1804
1805
        if ( ! isset($this->identifier[0])) {
1806
            throw MappingException::noIdDefined($this->name);
1807
        }
1808
1809
        return $this->identifier[0];
1810
    }
1811
1812
    /**
1813
     * Gets the column name of the single id column. Note that this only works on
1814
     * entity classes that have a single-field pk.
1815
     *
1816
     * @return string
1817
     *
1818
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1819
     */
1820
    public function getSingleIdentifierColumnName()
1821
    {
1822
        return $this->getColumnName($this->getSingleIdentifierFieldName());
1823
    }
1824
1825
    /**
1826
     * INTERNAL:
1827
     * Sets the mapped identifier/primary key fields of this class.
1828
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
1829
     *
1830
     * @param array $identifier
1831
     *
1832
     * @return void
1833
     */
1834
    public function setIdentifier(array $identifier)
1835
    {
1836
        $this->identifier = $identifier;
1837
        $this->isIdentifierComposite = (count($this->identifier) > 1);
1838
    }
1839
1840
    /**
1841
     * {@inheritDoc}
1842
     */
1843
    public function getIdentifier()
1844
    {
1845
        return $this->identifier;
1846
    }
1847
1848
    /**
1849
     * {@inheritDoc}
1850
     */
1851
    public function hasField($fieldName)
1852
    {
1853
        return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
1854
    }
1855
1856
    /**
1857
     * Gets an array containing all the column names.
1858
     *
1859
     * @param array|null $fieldNames
1860
     *
1861
     * @return array
1862
     */
1863
    public function getColumnNames(array $fieldNames = null)
1864
    {
1865
        if (null === $fieldNames) {
1866
            return array_keys($this->fieldNames);
1867
        }
1868
1869
        return array_values(array_map([$this, 'getColumnName'], $fieldNames));
1870
    }
1871
1872
    /**
1873
     * Returns an array with all the identifier column names.
1874
     *
1875
     * @return array
1876
     */
1877
    public function getIdentifierColumnNames()
1878
    {
1879
        $columnNames = [];
1880
1881
        foreach ($this->identifier as $idProperty) {
1882
            if (isset($this->fieldMappings[$idProperty])) {
1883
                $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
1884
1885
                continue;
1886
            }
1887
1888
            // Association defined as Id field
1889
            $joinColumns      = $this->associationMappings[$idProperty]['joinColumns'];
1890
            $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
1891
1892
            $columnNames = array_merge($columnNames, $assocColumnNames);
1893
        }
1894
1895
        return $columnNames;
1896
    }
1897
1898
    /**
1899
     * Sets the type of Id generator to use for the mapped class.
1900
     *
1901
     * @param int $generatorType
1902
     *
1903
     * @return void
1904
     */
1905
    public function setIdGeneratorType($generatorType)
1906
    {
1907
        $this->generatorType = $generatorType;
1908
    }
1909
1910
    /**
1911
     * Checks whether the mapped class uses an Id generator.
1912
     *
1913
     * @return boolean TRUE if the mapped class uses an Id generator, FALSE otherwise.
1914
     */
1915
    public function usesIdGenerator()
1916
    {
1917
        return $this->generatorType != self::GENERATOR_TYPE_NONE;
1918
    }
1919
1920
    /**
1921
     * @return boolean
1922
     */
1923
    public function isInheritanceTypeNone()
1924
    {
1925
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1926
    }
1927
1928
    /**
1929
     * Checks whether the mapped class uses the JOINED inheritance mapping strategy.
1930
     *
1931
     * @return boolean TRUE if the class participates in a JOINED inheritance mapping,
1932
     *                 FALSE otherwise.
1933
     */
1934
    public function isInheritanceTypeJoined()
1935
    {
1936
        return $this->inheritanceType == self::INHERITANCE_TYPE_JOINED;
1937
    }
1938
1939
    /**
1940
     * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy.
1941
     *
1942
     * @return boolean TRUE if the class participates in a SINGLE_TABLE inheritance mapping,
1943
     *                 FALSE otherwise.
1944
     */
1945
    public function isInheritanceTypeSingleTable()
1946
    {
1947
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE;
1948
    }
1949
1950
    /**
1951
     * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy.
1952
     *
1953
     * @return boolean TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping,
1954
     *                 FALSE otherwise.
1955
     */
1956
    public function isInheritanceTypeTablePerClass()
1957
    {
1958
        return $this->inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
1959
    }
1960
1961
    /**
1962
     * Checks whether the class uses an identity column for the Id generation.
1963
     *
1964
     * @return boolean TRUE if the class uses the IDENTITY generator, FALSE otherwise.
1965
     */
1966
    public function isIdGeneratorIdentity()
1967
    {
1968
        return $this->generatorType == self::GENERATOR_TYPE_IDENTITY;
1969
    }
1970
1971
    /**
1972
     * Checks whether the class uses a sequence for id generation.
1973
     *
1974
     * @return boolean TRUE if the class uses the SEQUENCE generator, FALSE otherwise.
1975
     */
1976
    public function isIdGeneratorSequence()
1977
    {
1978
        return $this->generatorType == self::GENERATOR_TYPE_SEQUENCE;
1979
    }
1980
1981
    /**
1982
     * Checks whether the class uses a table for id generation.
1983
     *
1984
     * @return boolean TRUE if the class uses the TABLE generator, FALSE otherwise.
1985
     */
1986
    public function isIdGeneratorTable()
1987
    {
1988
        return $this->generatorType == self::GENERATOR_TYPE_TABLE;
1989
    }
1990
1991
    /**
1992
     * Checks whether the class has a natural identifier/pk (which means it does
1993
     * not use any Id generator.
1994
     *
1995
     * @return boolean
1996
     */
1997
    public function isIdentifierNatural()
1998
    {
1999
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
2000
    }
2001
2002
    /**
2003
     * Checks whether the class use a UUID for id generation.
2004
     *
2005
     * @return boolean
2006
     */
2007
    public function isIdentifierUuid()
2008
    {
2009
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
2010
    }
2011
2012
    /**
2013
     * Gets the type of a field.
2014
     *
2015
     * @param string $fieldName
2016
     *
2017
     * @return \Doctrine\DBAL\Types\Type|string|null
2018
     *
2019
     * @todo 3.0 Remove this. PersisterHelper should fix it somehow
2020
     */
2021
    public function getTypeOfField($fieldName)
2022
    {
2023
        return isset($this->fieldMappings[$fieldName])
2024
            ? $this->fieldMappings[$fieldName]['type']
2025
            : null;
2026
    }
2027
2028
    /**
2029
     * Gets the type of a column.
2030
     *
2031
     * @param string $columnName
2032
     *
2033
     * @return \Doctrine\DBAL\Types\Type|string|null
2034
     *
2035
     * @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
2036
     *             that is derived by a referenced field on a different entity.
2037
     */
2038
    public function getTypeOfColumn($columnName)
2039
    {
2040
        return $this->getTypeOfField($this->getFieldName($columnName));
2041
    }
2042
2043
    /**
2044
     * Gets the name of the primary table.
2045
     *
2046
     * @return string
2047
     */
2048
    public function getTableName()
2049
    {
2050
        return $this->table['name'];
2051
    }
2052
2053
    /**
2054
     * Gets primary table's schema name.
2055
     *
2056
     * @return string|null
2057
     */
2058
    public function getSchemaName()
2059
    {
2060
        return isset($this->table['schema']) ? $this->table['schema'] : null;
2061
    }
2062
2063
    /**
2064
     * Gets the table name to use for temporary identifier tables of this class.
2065
     *
2066
     * @return string
2067
     */
2068
    public function getTemporaryIdTableName()
2069
    {
2070
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
2071
        return str_replace('.', '_', $this->getTableName() . '_id_tmp');
2072
    }
2073
2074
    /**
2075
     * Sets the mapped subclasses of this class.
2076
     *
2077
     * @param array $subclasses The names of all mapped subclasses.
2078
     *
2079
     * @return void
2080
     */
2081
    public function setSubclasses(array $subclasses)
2082
    {
2083
        foreach ($subclasses as $subclass) {
2084
            $this->subClasses[] = $this->fullyQualifiedClassName($subclass);
2085
        }
2086
    }
2087
2088
    /**
2089
     * Sets the parent class names.
2090
     * Assumes that the class names in the passed array are in the order:
2091
     * directParent -> directParentParent -> directParentParentParent ... -> root.
2092
     *
2093
     * @param array $classNames
2094
     *
2095
     * @return void
2096
     */
2097
    public function setParentClasses(array $classNames)
2098
    {
2099
        $this->parentClasses = $classNames;
2100
2101
        if (count($classNames) > 0) {
2102
            $this->rootEntityName = array_pop($classNames);
2103
        }
2104
    }
2105
2106
    /**
2107
     * Sets the inheritance type used by the class and its subclasses.
2108
     *
2109
     * @param integer $type
2110
     *
2111
     * @return void
2112
     *
2113
     * @throws MappingException
2114
     */
2115
    public function setInheritanceType($type)
2116
    {
2117
        if ( ! $this->_isInheritanceType($type)) {
2118
            throw MappingException::invalidInheritanceType($this->name, $type);
2119
        }
2120
2121
        $this->inheritanceType = $type;
2122
    }
2123
2124
    /**
2125
     * Sets the association to override association mapping of property for an entity relationship.
2126
     *
2127
     * @param string $fieldName
2128
     * @param array  $overrideMapping
2129
     *
2130
     * @return void
2131
     *
2132
     * @throws MappingException
2133
     */
2134
    public function setAssociationOverride($fieldName, array $overrideMapping)
2135
    {
2136
        if ( ! isset($this->associationMappings[$fieldName])) {
2137
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2138
        }
2139
2140
        $mapping = $this->associationMappings[$fieldName];
2141
2142
        if (isset($overrideMapping['joinColumns'])) {
2143
            $mapping['joinColumns'] = $overrideMapping['joinColumns'];
2144
        }
2145
2146
        if (isset($overrideMapping['inversedBy'])) {
2147
            $mapping['inversedBy'] = $overrideMapping['inversedBy'];
2148
        }
2149
2150
        if (isset($overrideMapping['joinTable'])) {
2151
            $mapping['joinTable'] = $overrideMapping['joinTable'];
2152
        }
2153
2154
        if (isset($overrideMapping['fetch'])) {
2155
            $mapping['fetch'] = $overrideMapping['fetch'];
2156
        }
2157
2158
        $mapping['joinColumnFieldNames']        = null;
2159
        $mapping['joinTableColumns']            = null;
2160
        $mapping['sourceToTargetKeyColumns']    = null;
2161
        $mapping['relationToSourceKeyColumns']  = null;
2162
        $mapping['relationToTargetKeyColumns']  = null;
2163
2164
        switch ($mapping['type']) {
2165
            case self::ONE_TO_ONE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2166
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2167
                break;
2168
            case self::ONE_TO_MANY:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2169
                $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2170
                break;
2171
            case self::MANY_TO_ONE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2172
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2173
                break;
2174
            case self::MANY_TO_MANY:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
2175
                $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2176
                break;
2177
        }
2178
2179
        $this->associationMappings[$fieldName] = $mapping;
2180
    }
2181
2182
    /**
2183
     * Sets the override for a mapped field.
2184
     *
2185
     * @param string $fieldName
2186
     * @param array  $overrideMapping
2187
     *
2188
     * @return void
2189
     *
2190
     * @throws MappingException
2191
     */
2192
    public function setAttributeOverride($fieldName, array $overrideMapping)
2193
    {
2194
        if ( ! isset($this->fieldMappings[$fieldName])) {
2195
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2196
        }
2197
2198
        $mapping = $this->fieldMappings[$fieldName];
2199
2200
        if (isset($mapping['id'])) {
2201
            $overrideMapping['id'] = $mapping['id'];
2202
        }
2203
2204
        if ( ! isset($overrideMapping['type'])) {
2205
            $overrideMapping['type'] = $mapping['type'];
2206
        }
2207
2208
        if ( ! isset($overrideMapping['fieldName'])) {
2209
            $overrideMapping['fieldName'] = $mapping['fieldName'];
2210
        }
2211
2212
        if ($overrideMapping['type'] !== $mapping['type']) {
2213
            throw MappingException::invalidOverrideFieldType($this->name, $fieldName);
2214
        }
2215
2216
        unset($this->fieldMappings[$fieldName]);
2217
        unset($this->fieldNames[$mapping['columnName']]);
2218
        unset($this->columnNames[$mapping['fieldName']]);
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\ClassMetadata::$columnNames has been deprecated: 3.0 Remove this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2218
        unset(/** @scrutinizer ignore-deprecated */ $this->columnNames[$mapping['fieldName']]);

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2219
2220
        $this->_validateAndCompleteFieldMapping($overrideMapping);
2221
2222
        $this->fieldMappings[$fieldName] = $overrideMapping;
2223
    }
2224
2225
    /**
2226
     * Checks whether a mapped field is inherited from an entity superclass.
2227
     *
2228
     * @param string $fieldName
2229
     *
2230
     * @return bool TRUE if the field is inherited, FALSE otherwise.
2231
     */
2232
    public function isInheritedField($fieldName)
2233
    {
2234
        return isset($this->fieldMappings[$fieldName]['inherited']);
2235
    }
2236
2237
    /**
2238
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
2239
     *
2240
     * @return bool
2241
     */
2242
    public function isRootEntity()
2243
    {
2244
        return $this->name == $this->rootEntityName;
2245
    }
2246
2247
    /**
2248
     * Checks whether a mapped association field is inherited from a superclass.
2249
     *
2250
     * @param string $fieldName
2251
     *
2252
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
2253
     */
2254
    public function isInheritedAssociation($fieldName)
2255
    {
2256
        return isset($this->associationMappings[$fieldName]['inherited']);
2257
    }
2258
2259
    public function isInheritedEmbeddedClass($fieldName)
2260
    {
2261
        return isset($this->embeddedClasses[$fieldName]['inherited']);
2262
    }
2263
2264
    /**
2265
     * Sets the name of the primary table the class is mapped to.
2266
     *
2267
     * @param string $tableName The table name.
2268
     *
2269
     * @return void
2270
     *
2271
     * @deprecated Use {@link setPrimaryTable}.
2272
     */
2273
    public function setTableName($tableName)
2274
    {
2275
        $this->table['name'] = $tableName;
2276
    }
2277
2278
    /**
2279
     * Sets the primary table definition. The provided array supports the
2280
     * following structure:
2281
     *
2282
     * name => <tableName> (optional, defaults to class name)
2283
     * indexes => array of indexes (optional)
2284
     * uniqueConstraints => array of constraints (optional)
2285
     *
2286
     * If a key is omitted, the current value is kept.
2287
     *
2288
     * @param array $table The table description.
2289
     *
2290
     * @return void
2291
     */
2292
    public function setPrimaryTable(array $table)
2293
    {
2294
        if (isset($table['name'])) {
2295
            // Split schema and table name from a table name like "myschema.mytable"
2296
            if (strpos($table['name'], '.') !== false) {
2297
                list($this->table['schema'], $table['name']) = explode('.', $table['name'], 2);
2298
            }
2299
2300
            if ($table['name'][0] === '`') {
2301
                $table['name']          = trim($table['name'], '`');
2302
                $this->table['quoted']  = true;
2303
            }
2304
2305
            $this->table['name'] = $table['name'];
2306
        }
2307
2308
        if (isset($table['quoted'])) {
2309
            $this->table['quoted'] = $table['quoted'];
2310
        }
2311
2312
        if (isset($table['schema'])) {
2313
            $this->table['schema'] = $table['schema'];
2314
        }
2315
2316
        if (isset($table['indexes'])) {
2317
            $this->table['indexes'] = $table['indexes'];
2318
        }
2319
2320
        if (isset($table['uniqueConstraints'])) {
2321
            $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
2322
        }
2323
2324
        if (isset($table['options'])) {
2325
            $this->table['options'] = $table['options'];
2326
        }
2327
    }
2328
2329
    /**
2330
     * Checks whether the given type identifies an inheritance type.
2331
     *
2332
     * @param integer $type
2333
     *
2334
     * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise.
2335
     */
2336
    private function _isInheritanceType($type)
2337
    {
2338
        return $type == self::INHERITANCE_TYPE_NONE ||
2339
                $type == self::INHERITANCE_TYPE_SINGLE_TABLE ||
2340
                $type == self::INHERITANCE_TYPE_JOINED ||
2341
                $type == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
2342
    }
2343
2344
    /**
2345
     * Adds a mapped field to the class.
2346
     *
2347
     * @param array $mapping The field mapping.
2348
     *
2349
     * @return void
2350
     *
2351
     * @throws MappingException
2352
     */
2353
    public function mapField(array $mapping)
2354
    {
2355
        $this->_validateAndCompleteFieldMapping($mapping);
2356
        $this->assertFieldNotMapped($mapping['fieldName']);
2357
2358
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2359
    }
2360
2361
    /**
2362
     * INTERNAL:
2363
     * Adds an association mapping without completing/validating it.
2364
     * This is mainly used to add inherited association mappings to derived classes.
2365
     *
2366
     * @param array $mapping
2367
     *
2368
     * @return void
2369
     *
2370
     * @throws MappingException
2371
     */
2372
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% 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...
2373
    {
2374
        if (isset($this->associationMappings[$mapping['fieldName']])) {
2375
            throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']);
2376
        }
2377
        $this->associationMappings[$mapping['fieldName']] = $mapping;
2378
    }
2379
2380
    /**
2381
     * INTERNAL:
2382
     * Adds a field mapping without completing/validating it.
2383
     * This is mainly used to add inherited field mappings to derived classes.
2384
     *
2385
     * @param array $fieldMapping
2386
     *
2387
     * @return void
2388
     */
2389
    public function addInheritedFieldMapping(array $fieldMapping)
2390
    {
2391
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
2392
        $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\ClassMetadata::$columnNames has been deprecated: 3.0 Remove this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2392
        /** @scrutinizer ignore-deprecated */ $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2393
        $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
2394
    }
2395
2396
    /**
2397
     * INTERNAL:
2398
     * Adds a named query to this class.
2399
     *
2400
     * @param array $queryMapping
2401
     *
2402
     * @return void
2403
     *
2404
     * @throws MappingException
2405
     */
2406
    public function addNamedQuery(array $queryMapping)
2407
    {
2408
        if (!isset($queryMapping['name'])) {
2409
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2410
        }
2411
2412 View Code Duplication
        if (isset($this->namedQueries[$queryMapping['name']])) {
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...
2413
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2414
        }
2415
2416
        if (!isset($queryMapping['query'])) {
2417
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2418
        }
2419
2420
        $name   = $queryMapping['name'];
2421
        $query  = $queryMapping['query'];
2422
        $dql    = str_replace('__CLASS__', $this->name, $query);
2423
2424
        $this->namedQueries[$name] = [
2425
            'name'  => $name,
2426
            'query' => $query,
2427
            'dql'   => $dql,
2428
        ];
2429
    }
2430
2431
    /**
2432
     * INTERNAL:
2433
     * Adds a named native query to this class.
2434
     *
2435
     * @param array $queryMapping
2436
     *
2437
     * @return void
2438
     *
2439
     * @throws MappingException
2440
     */
2441
    public function addNamedNativeQuery(array $queryMapping)
2442
    {
2443
        if (!isset($queryMapping['name'])) {
2444
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2445
        }
2446
2447 View Code Duplication
        if (isset($this->namedNativeQueries[$queryMapping['name']])) {
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...
2448
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2449
        }
2450
2451
        if (!isset($queryMapping['query'])) {
2452
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2453
        }
2454
2455
        if (!isset($queryMapping['resultClass']) && !isset($queryMapping['resultSetMapping'])) {
2456
            throw MappingException::missingQueryMapping($this->name, $queryMapping['name']);
2457
        }
2458
2459
        $queryMapping['isSelfClass'] = false;
2460
2461
        if (isset($queryMapping['resultClass'])) {
2462
            if ($queryMapping['resultClass'] === '__CLASS__') {
2463
2464
                $queryMapping['isSelfClass'] = true;
2465
                $queryMapping['resultClass'] = $this->name;
2466
            }
2467
2468
            $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
2469
            $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\');
2470
        }
2471
2472
        $this->namedNativeQueries[$queryMapping['name']] = $queryMapping;
2473
    }
2474
2475
    /**
2476
     * INTERNAL:
2477
     * Adds a sql result set mapping to this class.
2478
     *
2479
     * @param array $resultMapping
2480
     *
2481
     * @return void
2482
     *
2483
     * @throws MappingException
2484
     */
2485
    public function addSqlResultSetMapping(array $resultMapping)
2486
    {
2487
        if (!isset($resultMapping['name'])) {
2488
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name);
2489
        }
2490
2491
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
2492
            throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']);
2493
        }
2494
2495
        if (isset($resultMapping['entities'])) {
2496
            foreach ($resultMapping['entities'] as $key => $entityResult) {
2497
                if (!isset($entityResult['entityClass'])) {
2498
                    throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']);
2499
                }
2500
2501
                $entityResult['isSelfClass'] = false;
2502
                if ($entityResult['entityClass'] === '__CLASS__') {
2503
2504
                    $entityResult['isSelfClass'] = true;
2505
                    $entityResult['entityClass'] = $this->name;
2506
2507
                }
2508
2509
                $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']);
2510
2511
                $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
2512
                $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
2513
2514
                if (isset($entityResult['fields'])) {
2515
                    foreach ($entityResult['fields'] as $k => $field) {
2516
                        if (!isset($field['name'])) {
2517
                            throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']);
2518
                        }
2519
2520
                        if (!isset($field['column'])) {
2521
                            $fieldName = $field['name'];
2522
                            if (strpos($fieldName, '.')) {
2523
                                list(, $fieldName) = explode('.', $fieldName);
2524
                            }
2525
2526
                            $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
2527
                        }
2528
                    }
2529
                }
2530
            }
2531
        }
2532
2533
        $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
2534
    }
2535
2536
    /**
2537
     * Adds a one-to-one mapping.
2538
     *
2539
     * @param array $mapping The mapping.
2540
     *
2541
     * @return void
2542
     */
2543
    public function mapOneToOne(array $mapping)
2544
    {
2545
        $mapping['type'] = self::ONE_TO_ONE;
2546
2547
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2548
2549
        $this->_storeAssociationMapping($mapping);
2550
    }
2551
2552
    /**
2553
     * Adds a one-to-many mapping.
2554
     *
2555
     * @param array $mapping The mapping.
2556
     *
2557
     * @return void
2558
     */
2559
    public function mapOneToMany(array $mapping)
2560
    {
2561
        $mapping['type'] = self::ONE_TO_MANY;
2562
2563
        $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2564
2565
        $this->_storeAssociationMapping($mapping);
2566
    }
2567
2568
    /**
2569
     * Adds a many-to-one mapping.
2570
     *
2571
     * @param array $mapping The mapping.
2572
     *
2573
     * @return void
2574
     */
2575
    public function mapManyToOne(array $mapping)
2576
    {
2577
        $mapping['type'] = self::MANY_TO_ONE;
2578
2579
        // A many-to-one mapping is essentially a one-one backreference
2580
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2581
2582
        $this->_storeAssociationMapping($mapping);
2583
    }
2584
2585
    /**
2586
     * Adds a many-to-many mapping.
2587
     *
2588
     * @param array $mapping The mapping.
2589
     *
2590
     * @return void
2591
     */
2592
    public function mapManyToMany(array $mapping)
2593
    {
2594
        $mapping['type'] = self::MANY_TO_MANY;
2595
2596
        $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2597
2598
        $this->_storeAssociationMapping($mapping);
2599
    }
2600
2601
    /**
2602
     * Stores the association mapping.
2603
     *
2604
     * @param array $assocMapping
2605
     *
2606
     * @return void
2607
     *
2608
     * @throws MappingException
2609
     */
2610
    protected function _storeAssociationMapping(array $assocMapping)
2611
    {
2612
        $sourceFieldName = $assocMapping['fieldName'];
2613
2614
        $this->assertFieldNotMapped($sourceFieldName);
2615
2616
        $this->associationMappings[$sourceFieldName] = $assocMapping;
2617
    }
2618
2619
    /**
2620
     * Registers a custom repository class for the entity class.
2621
     *
2622
     * @param string $repositoryClassName The class name of the custom mapper.
2623
     *
2624
     * @return void
2625
     */
2626
    public function setCustomRepositoryClass($repositoryClassName)
2627
    {
2628
        $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
2629
    }
2630
2631
    /**
2632
     * Dispatches the lifecycle event of the given entity to the registered
2633
     * lifecycle callbacks and lifecycle listeners.
2634
     *
2635
     * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker
2636
     *
2637
     * @param string $lifecycleEvent The lifecycle event.
2638
     * @param object $entity         The Entity on which the event occurred.
2639
     *
2640
     * @return void
2641
     */
2642
    public function invokeLifecycleCallbacks($lifecycleEvent, $entity)
2643
    {
2644
        foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) {
2645
            $entity->$callback();
2646
        }
2647
    }
2648
2649
    /**
2650
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
2651
     *
2652
     * @param string $lifecycleEvent
2653
     *
2654
     * @return boolean
2655
     */
2656
    public function hasLifecycleCallbacks($lifecycleEvent)
2657
    {
2658
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
2659
    }
2660
2661
    /**
2662
     * Gets the registered lifecycle callbacks for an event.
2663
     *
2664
     * @param string $event
2665
     *
2666
     * @return array
2667
     */
2668
    public function getLifecycleCallbacks($event)
2669
    {
2670
        return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : [];
2671
    }
2672
2673
    /**
2674
     * Adds a lifecycle callback for entities of this class.
2675
     *
2676
     * @param string $callback
2677
     * @param string $event
2678
     *
2679
     * @return void
2680
     */
2681
    public function addLifecycleCallback($callback, $event)
2682
    {
2683
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
2684
            return;
2685
        }
2686
2687
        $this->lifecycleCallbacks[$event][] = $callback;
2688
    }
2689
2690
    /**
2691
     * Sets the lifecycle callbacks for entities of this class.
2692
     * Any previously registered callbacks are overwritten.
2693
     *
2694
     * @param array $callbacks
2695
     *
2696
     * @return void
2697
     */
2698
    public function setLifecycleCallbacks(array $callbacks)
2699
    {
2700
        $this->lifecycleCallbacks = $callbacks;
2701
    }
2702
2703
    /**
2704
     * Adds a entity listener for entities of this class.
2705
     *
2706
     * @param string $eventName The entity lifecycle event.
2707
     * @param string $class     The listener class.
2708
     * @param string $method    The listener callback method.
2709
     *
2710
     * @throws \Doctrine\ORM\Mapping\MappingException
2711
     */
2712
    public function addEntityListener($eventName, $class, $method)
2713
    {
2714
        $class    = $this->fullyQualifiedClassName($class);
2715
2716
        $listener = [
2717
            'class'  => $class,
2718
            'method' => $method,
2719
        ];
2720
2721
        if ( ! class_exists($class)) {
2722
            throw MappingException::entityListenerClassNotFound($class, $this->name);
2723
        }
2724
2725
        if ( ! method_exists($class, $method)) {
2726
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->name);
2727
        }
2728
2729
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName])) {
2730
            throw MappingException::duplicateEntityListener($class, $method, $this->name);
2731
        }
2732
2733
        $this->entityListeners[$eventName][] = $listener;
2734
    }
2735
2736
    /**
2737
     * Sets the discriminator column definition.
2738
     *
2739
     * @param array $columnDef
2740
     *
2741
     * @return void
2742
     *
2743
     * @throws MappingException
2744
     *
2745
     * @see getDiscriminatorColumn()
2746
     */
2747
    public function setDiscriminatorColumn($columnDef)
2748
    {
2749
        if ($columnDef !== null) {
2750
            if ( ! isset($columnDef['name'])) {
2751
                throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name);
2752
            }
2753
2754
            if (isset($this->fieldNames[$columnDef['name']])) {
2755
                throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
2756
            }
2757
2758
            if ( ! isset($columnDef['fieldName'])) {
2759
                $columnDef['fieldName'] = $columnDef['name'];
2760
            }
2761
2762
            if ( ! isset($columnDef['type'])) {
2763
                $columnDef['type'] = "string";
2764
            }
2765
2766
            if (in_array($columnDef['type'], ["boolean", "array", "object", "datetime", "time", "date"])) {
2767
                throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
2768
            }
2769
2770
            $this->discriminatorColumn = $columnDef;
2771
        }
2772
    }
2773
2774
    /**
2775
     * Sets the discriminator values used by this class.
2776
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
2777
     *
2778
     * @param array $map
2779
     *
2780
     * @return void
2781
     */
2782
    public function setDiscriminatorMap(array $map)
2783
    {
2784
        foreach ($map as $value => $className) {
2785
            $this->addDiscriminatorMapClass($value, $className);
2786
        }
2787
    }
2788
2789
    /**
2790
     * Adds one entry of the discriminator map with a new class and corresponding name.
2791
     *
2792
     * @param string $name
2793
     * @param string $className
2794
     *
2795
     * @return void
2796
     *
2797
     * @throws MappingException
2798
     */
2799
    public function addDiscriminatorMapClass($name, $className)
2800
    {
2801
        $className = $this->fullyQualifiedClassName($className);
2802
        $className = ltrim($className, '\\');
2803
2804
        $this->discriminatorMap[$name] = $className;
2805
2806
        if ($this->name === $className) {
2807
            $this->discriminatorValue = $name;
2808
2809
            return;
2810
        }
2811
2812
        if ( ! (class_exists($className) || interface_exists($className))) {
2813
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
2814
        }
2815
2816
        if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) {
2817
            $this->subClasses[] = $className;
2818
        }
2819
    }
2820
2821
    /**
2822
     * Checks whether the class has a named query with the given query name.
2823
     *
2824
     * @param string $queryName
2825
     *
2826
     * @return boolean
2827
     */
2828
    public function hasNamedQuery($queryName)
2829
    {
2830
        return isset($this->namedQueries[$queryName]);
2831
    }
2832
2833
    /**
2834
     * Checks whether the class has a named native query with the given query name.
2835
     *
2836
     * @param string $queryName
2837
     *
2838
     * @return boolean
2839
     */
2840
    public function hasNamedNativeQuery($queryName)
2841
    {
2842
        return isset($this->namedNativeQueries[$queryName]);
2843
    }
2844
2845
    /**
2846
     * Checks whether the class has a named native query with the given query name.
2847
     *
2848
     * @param string $name
2849
     *
2850
     * @return boolean
2851
     */
2852
    public function hasSqlResultSetMapping($name)
2853
    {
2854
        return isset($this->sqlResultSetMappings[$name]);
2855
    }
2856
2857
    /**
2858
     * {@inheritDoc}
2859
     */
2860
    public function hasAssociation($fieldName)
2861
    {
2862
        return isset($this->associationMappings[$fieldName]);
2863
    }
2864
2865
    /**
2866
     * {@inheritDoc}
2867
     */
2868
    public function isSingleValuedAssociation($fieldName)
2869
    {
2870
        return isset($this->associationMappings[$fieldName])
2871
            && ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2872
    }
2873
2874
    /**
2875
     * {@inheritDoc}
2876
     */
2877
    public function isCollectionValuedAssociation($fieldName)
2878
    {
2879
        return isset($this->associationMappings[$fieldName])
2880
            && ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2881
    }
2882
2883
    /**
2884
     * Is this an association that only has a single join column?
2885
     *
2886
     * @param string $fieldName
2887
     *
2888
     * @return bool
2889
     */
2890
    public function isAssociationWithSingleJoinColumn($fieldName)
2891
    {
2892
        return isset($this->associationMappings[$fieldName])
2893
            && isset($this->associationMappings[$fieldName]['joinColumns'][0])
2894
            && ! isset($this->associationMappings[$fieldName]['joinColumns'][1]);
2895
    }
2896
2897
    /**
2898
     * Returns the single association join column (if any).
2899
     *
2900
     * @param string $fieldName
2901
     *
2902
     * @return string
2903
     *
2904
     * @throws MappingException
2905
     */
2906 View Code Duplication
    public function getSingleAssociationJoinColumnName($fieldName)
2907
    {
2908
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2909
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2910
        }
2911
2912
        return $this->associationMappings[$fieldName]['joinColumns'][0]['name'];
2913
    }
2914
2915
    /**
2916
     * Returns the single association referenced join column name (if any).
2917
     *
2918
     * @param string $fieldName
2919
     *
2920
     * @return string
2921
     *
2922
     * @throws MappingException
2923
     */
2924 View Code Duplication
    public function getSingleAssociationReferencedJoinColumnName($fieldName)
2925
    {
2926
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2927
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2928
        }
2929
2930
        return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName'];
2931
    }
2932
2933
    /**
2934
     * Used to retrieve a fieldname for either field or association from a given column.
2935
     *
2936
     * This method is used in foreign-key as primary-key contexts.
2937
     *
2938
     * @param string $columnName
2939
     *
2940
     * @return string
2941
     *
2942
     * @throws MappingException
2943
     */
2944
    public function getFieldForColumn($columnName)
2945
    {
2946
        if (isset($this->fieldNames[$columnName])) {
2947
            return $this->fieldNames[$columnName];
2948
        }
2949
2950
        foreach ($this->associationMappings as $assocName => $mapping) {
2951
            if ($this->isAssociationWithSingleJoinColumn($assocName) &&
2952
                $this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) {
2953
2954
                return $assocName;
2955
            }
2956
        }
2957
2958
        throw MappingException::noFieldNameFoundForColumn($this->name, $columnName);
2959
    }
2960
2961
    /**
2962
     * Sets the ID generator used to generate IDs for instances of this class.
2963
     *
2964
     * @param \Doctrine\ORM\Id\AbstractIdGenerator $generator
2965
     *
2966
     * @return void
2967
     */
2968
    public function setIdGenerator($generator)
2969
    {
2970
        $this->idGenerator = $generator;
2971
    }
2972
2973
    /**
2974
     * Sets definition.
2975
     *
2976
     * @param array $definition
2977
     *
2978
     * @return void
2979
     */
2980
    public function setCustomGeneratorDefinition(array $definition)
2981
    {
2982
        $this->customGeneratorDefinition = $definition;
2983
    }
2984
2985
    /**
2986
     * Sets the definition of the sequence ID generator for this class.
2987
     *
2988
     * The definition must have the following structure:
2989
     * <code>
2990
     * array(
2991
     *     'sequenceName'   => 'name',
2992
     *     'allocationSize' => 20,
2993
     *     'initialValue'   => 1
2994
     *     'quoted'         => 1
2995
     * )
2996
     * </code>
2997
     *
2998
     * @param array $definition
2999
     *
3000
     * @return void
3001
     *
3002
     * @throws MappingException
3003
     */
3004
    public function setSequenceGeneratorDefinition(array $definition)
3005
    {
3006
        if ( ! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') {
3007
            throw MappingException::missingSequenceName($this->name);
3008
        }
3009
3010
        if ($definition['sequenceName'][0] == '`') {
3011
            $definition['sequenceName']   = trim($definition['sequenceName'], '`');
3012
            $definition['quoted'] = true;
3013
        }
3014
3015
        if ( ! isset($definition['allocationSize']) || trim($definition['allocationSize']) === '') {
3016
            $definition['allocationSize'] = '1';
3017
        }
3018
3019
        if ( ! isset($definition['initialValue']) || trim($definition['initialValue']) === '') {
3020
            $definition['initialValue'] = '1';
3021
        }
3022
3023
        $this->sequenceGeneratorDefinition = $definition;
3024
    }
3025
3026
    /**
3027
     * Sets the version field mapping used for versioning. Sets the default
3028
     * value to use depending on the column type.
3029
     *
3030
     * @param array $mapping The version field mapping array.
3031
     *
3032
     * @return void
3033
     *
3034
     * @throws MappingException
3035
     */
3036
    public function setVersionMapping(array &$mapping)
3037
    {
3038
        $this->isVersioned = true;
3039
        $this->versionField = $mapping['fieldName'];
3040
3041
        if ( ! isset($mapping['default'])) {
3042
            if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'])) {
3043
                $mapping['default'] = 1;
3044
            } else if ($mapping['type'] == 'datetime') {
3045
                $mapping['default'] = 'CURRENT_TIMESTAMP';
3046
            } else {
3047
                throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']);
3048
            }
3049
        }
3050
    }
3051
3052
    /**
3053
     * Sets whether this class is to be versioned for optimistic locking.
3054
     *
3055
     * @param boolean $bool
3056
     *
3057
     * @return void
3058
     */
3059
    public function setVersioned($bool)
3060
    {
3061
        $this->isVersioned = $bool;
3062
    }
3063
3064
    /**
3065
     * Sets the name of the field that is to be used for versioning if this class is
3066
     * versioned for optimistic locking.
3067
     *
3068
     * @param string $versionField
3069
     *
3070
     * @return void
3071
     */
3072
    public function setVersionField($versionField)
3073
    {
3074
        $this->versionField = $versionField;
3075
    }
3076
3077
    /**
3078
     * Marks this class as read only, no change tracking is applied to it.
3079
     *
3080
     * @return void
3081
     */
3082
    public function markReadOnly()
3083
    {
3084
        $this->isReadOnly = true;
3085
    }
3086
3087
    /**
3088
     * {@inheritDoc}
3089
     */
3090
    public function getFieldNames()
3091
    {
3092
        return array_keys($this->fieldMappings);
3093
    }
3094
3095
    /**
3096
     * {@inheritDoc}
3097
     */
3098
    public function getAssociationNames()
3099
    {
3100
        return array_keys($this->associationMappings);
3101
    }
3102
3103
    /**
3104
     * {@inheritDoc}
3105
     *
3106
     * @throws InvalidArgumentException
3107
     */
3108
    public function getAssociationTargetClass($assocName)
3109
    {
3110
        if ( ! isset($this->associationMappings[$assocName])) {
3111
            throw new InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
3112
        }
3113
3114
        return $this->associationMappings[$assocName]['targetEntity'];
3115
    }
3116
3117
    /**
3118
     * {@inheritDoc}
3119
     */
3120
    public function getName()
3121
    {
3122
        return $this->name;
3123
    }
3124
3125
    /**
3126
     * Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
3127
     *
3128
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3129
     *
3130
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3131
     *
3132
     * @return array
3133
     */
3134
    public function getQuotedIdentifierColumnNames($platform)
3135
    {
3136
        $quotedColumnNames = [];
3137
3138
        foreach ($this->identifier as $idProperty) {
3139
            if (isset($this->fieldMappings[$idProperty])) {
3140
                $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
3141
                    ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
3142
                    : $this->fieldMappings[$idProperty]['columnName'];
3143
3144
                continue;
3145
            }
3146
3147
            // Association defined as Id field
3148
            $joinColumns            = $this->associationMappings[$idProperty]['joinColumns'];
3149
            $assocQuotedColumnNames = array_map(
3150 View Code Duplication
                function ($joinColumn) use ($platform) {
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...
3151
                    return isset($joinColumn['quoted'])
3152
                        ? $platform->quoteIdentifier($joinColumn['name'])
3153
                        : $joinColumn['name'];
3154
                },
3155
                $joinColumns
3156
            );
3157
3158
            $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
3159
        }
3160
3161
        return $quotedColumnNames;
3162
    }
3163
3164
    /**
3165
     * Gets the (possibly quoted) column name of a mapped field for safe use  in an SQL statement.
3166
     *
3167
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3168
     *
3169
     * @param string                                    $field
3170
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3171
     *
3172
     * @return string
3173
     */
3174
    public function getQuotedColumnName($field, $platform)
3175
    {
3176
        return isset($this->fieldMappings[$field]['quoted'])
3177
            ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName'])
3178
            : $this->fieldMappings[$field]['columnName'];
3179
    }
3180
3181
    /**
3182
     * Gets the (possibly quoted) primary table name of this class for safe use in an SQL statement.
3183
     *
3184
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3185
     *
3186
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3187
     *
3188
     * @return string
3189
     */
3190
    public function getQuotedTableName($platform)
3191
    {
3192
        return isset($this->table['quoted'])
3193
            ? $platform->quoteIdentifier($this->table['name'])
3194
            : $this->table['name'];
3195
    }
3196
3197
    /**
3198
     * Gets the (possibly quoted) name of the join table.
3199
     *
3200
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3201
     *
3202
     * @param array                                     $assoc
3203
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3204
     *
3205
     * @return string
3206
     */
3207
    public function getQuotedJoinTableName(array $assoc, $platform)
3208
    {
3209
        return isset($assoc['joinTable']['quoted'])
3210
            ? $platform->quoteIdentifier($assoc['joinTable']['name'])
3211
            : $assoc['joinTable']['name'];
3212
    }
3213
3214
    /**
3215
     * {@inheritDoc}
3216
     */
3217
    public function isAssociationInverseSide($fieldName)
3218
    {
3219
        return isset($this->associationMappings[$fieldName])
3220
            && ! $this->associationMappings[$fieldName]['isOwningSide'];
3221
    }
3222
3223
    /**
3224
     * {@inheritDoc}
3225
     */
3226
    public function getAssociationMappedByTargetField($fieldName)
3227
    {
3228
        return $this->associationMappings[$fieldName]['mappedBy'];
3229
    }
3230
3231
    /**
3232
     * @param string $targetClass
3233
     *
3234
     * @return array
3235
     */
3236
    public function getAssociationsByTargetClass($targetClass)
3237
    {
3238
        $relations = [];
3239
3240
        foreach ($this->associationMappings as $mapping) {
3241
            if ($mapping['targetEntity'] == $targetClass) {
3242
                $relations[$mapping['fieldName']] = $mapping;
3243
            }
3244
        }
3245
3246
        return $relations;
3247
    }
3248
3249
    /**
3250
     * @param  string|null $className
3251
     *
3252
     * @return string|null null if the input value is null
3253
     */
3254
    public function fullyQualifiedClassName($className)
3255
    {
3256
        if (empty($className)) {
3257
            return $className;
3258
        }
3259
3260
        if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
3261
            return $this->namespace . '\\' . $className;
3262
        }
3263
3264
        return $className;
3265
    }
3266
3267
    /**
3268
     * @param string $name
3269
     *
3270
     * @return mixed
3271
     */
3272
    public function getMetadataValue($name)
3273
    {
3274
3275
        if (isset($this->$name)) {
3276
            return $this->$name;
3277
        }
3278
3279
        return null;
3280
    }
3281
3282
    /**
3283
     * Map Embedded Class
3284
     *
3285
     * @param array $mapping
3286
     *
3287
     * @throws MappingException
3288
     * @return void
3289
     */
3290
    public function mapEmbedded(array $mapping)
3291
    {
3292
        $this->assertFieldNotMapped($mapping['fieldName']);
3293
3294
        $this->embeddedClasses[$mapping['fieldName']] = [
3295
            'class' => $this->fullyQualifiedClassName($mapping['class']),
3296
            'columnPrefix' => $mapping['columnPrefix'],
3297
            'declaredField' => $mapping['declaredField'] ?? null,
3298
            'originalField' => $mapping['originalField'] ?? null,
3299
        ];
3300
    }
3301
3302
    /**
3303
     * Inline the embeddable class
3304
     *
3305
     * @param string            $property
3306
     * @param ClassMetadata $embeddable
3307
     */
3308
    public function inlineEmbeddable($property, ClassMetadata $embeddable)
3309
    {
3310
        foreach ($embeddable->fieldMappings as $fieldMapping) {
3311
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->name;
3312
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
3313
                ? $property . '.' . $fieldMapping['declaredField']
3314
                : $property;
3315
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldMapping['fieldName'];
3316
            $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName'];
3317
3318
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
3319
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
3320
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
3321
                $fieldMapping['columnName'] = $this->namingStrategy
3322
                    ->embeddedFieldToColumnName(
3323
                        $property,
3324
                        $fieldMapping['columnName'],
3325
                        $this->reflClass->name,
3326
                        $embeddable->reflClass->name
3327
                    );
3328
            }
3329
3330
            $this->mapField($fieldMapping);
3331
        }
3332
    }
3333
3334
    /**
3335
     * @param string $fieldName
3336
     * @throws MappingException
3337
     */
3338
    private function assertFieldNotMapped($fieldName)
3339
    {
3340
        if (isset($this->fieldMappings[$fieldName]) ||
3341
            isset($this->associationMappings[$fieldName]) ||
3342
            isset($this->embeddedClasses[$fieldName])) {
3343
3344
            throw MappingException::duplicateFieldMapping($this->name, $fieldName);
3345
        }
3346
    }
3347
3348
    /**
3349
     * Gets the sequence name based on class metadata.
3350
     *
3351
     * @param AbstractPlatform $platform
3352
     * @return string
3353
     *
3354
     * @todo Sequence names should be computed in DBAL depending on the platform
3355
     */
3356
    public function getSequenceName(AbstractPlatform $platform)
3357
    {
3358
        $sequencePrefix = $this->getSequencePrefix($platform);
3359
        $columnName     = $this->getSingleIdentifierColumnName();
3360
        $sequenceName   = $sequencePrefix . '_' . $columnName . '_seq';
3361
3362
        return $sequenceName;
3363
    }
3364
3365
    /**
3366
     * Gets the sequence name prefix based on class metadata.
3367
     *
3368
     * @param AbstractPlatform $platform
3369
     * @return string
3370
     *
3371
     * @todo Sequence names should be computed in DBAL depending on the platform
3372
     */
3373
    public function getSequencePrefix(AbstractPlatform $platform)
3374
    {
3375
        $tableName      = $this->getTableName();
3376
        $sequencePrefix = $tableName;
3377
3378
        // Prepend the schema name to the table name if there is one
3379
        if ($schemaName = $this->getSchemaName()) {
3380
            $sequencePrefix = $schemaName . '.' . $tableName;
3381
3382
            if ( ! $platform->supportsSchemas() && $platform->canEmulateSchemas()) {
3383
                $sequencePrefix = $schemaName . '__' . $tableName;
3384
            }
3385
        }
3386
3387
        return $sequencePrefix;
3388
    }
3389
3390
    /**
3391
     * @param array $mapping
3392
     */
3393
    private function assertMappingOrderBy(array $mapping)
3394
    {
3395
        if (isset($mapping['orderBy']) && !is_array($mapping['orderBy'])) {
3396
            throw new InvalidArgumentException("'orderBy' is expected to be an array, not " . gettype($mapping['orderBy']));
3397
        }
3398
    }
3399
}
3400
3401
class_alias(
3402
    'Doctrine\ORM\Mapping\ClassMetadata',
3403
    'Doctrine\ORM\Mapping\ClassMetadataInfo',
3404
    false
3405
);
3406