Failed Conditions
Pull Request — develop (#6873)
by
unknown
112:44 queued 47:41
created

ClassMetadataInfo::addNamedNativeQuery()   C

Complexity

Conditions 8
Paths 7

Size

Total Lines 32
Code Lines 16

Duplication

Lines 3
Ratio 9.38 %

Importance

Changes 0
Metric Value
cc 8
eloc 16
nc 7
nop 1
dl 3
loc 32
rs 5.3846
c 0
b 0
f 0
1
<?php
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;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Doctrine\ORM\Mapping\ClassMetadata. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
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 ClassMetadataInfo implements ClassMetadata
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
0 ignored issues
show
Bug introduced by
The type Doctrine\ORM\Id\AbstractIdGenerator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
0 ignored issues
show
Bug introduced by
The type Doctrine\ORM\Mapping\NamingStrategy was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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();
0 ignored issues
show
Bug introduced by
The type Doctrine\ORM\Mapping\DefaultNamingStrategy was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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] = $reflService->getAccessibleProperty(
964
                ($mapping['declared'] ?? $this->name), $field
965
            );
966
        }
967
968
        foreach ($this->associationMappings as $field => $mapping) {
969
            $this->reflFields[$field] = $reflService->getAccessibleProperty(
970
                ($mapping['declared'] ?? $this->name), $field
971
            );
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'] = $this->cache['usage'] ?? self::CACHE_USAGE_READ_ONLY;
1101
        }
1102
1103 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...
1104
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
1105
        }
1106
1107
        return $cache;
1108
    }
1109
1110
    /**
1111
     * Sets the change tracking policy used by this class.
1112
     *
1113
     * @param integer $policy
1114
     *
1115
     * @return void
1116
     */
1117
    public function setChangeTrackingPolicy($policy)
1118
    {
1119
        $this->changeTrackingPolicy = $policy;
1120
    }
1121
1122
    /**
1123
     * Whether the change tracking policy of this class is "deferred explicit".
1124
     *
1125
     * @return boolean
1126
     */
1127
    public function isChangeTrackingDeferredExplicit()
1128
    {
1129
        return self::CHANGETRACKING_DEFERRED_EXPLICIT === $this->changeTrackingPolicy;
1130
    }
1131
1132
    /**
1133
     * Whether the change tracking policy of this class is "deferred implicit".
1134
     *
1135
     * @return boolean
1136
     */
1137
    public function isChangeTrackingDeferredImplicit()
1138
    {
1139
        return self::CHANGETRACKING_DEFERRED_IMPLICIT === $this->changeTrackingPolicy;
1140
    }
1141
1142
    /**
1143
     * Whether the change tracking policy of this class is "notify".
1144
     *
1145
     * @return boolean
1146
     */
1147
    public function isChangeTrackingNotify()
1148
    {
1149
        return self::CHANGETRACKING_NOTIFY === $this->changeTrackingPolicy;
1150
    }
1151
1152
    /**
1153
     * Checks whether a field is part of the identifier/primary key field(s).
1154
     *
1155
     * @param string $fieldName The field name.
1156
     *
1157
     * @return boolean TRUE if the field is part of the table identifier/primary key field(s),
1158
     *                 FALSE otherwise.
1159
     */
1160
    public function isIdentifier($fieldName)
1161
    {
1162
        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...
1163
            return false;
1164
        }
1165
1166
        if ( ! $this->isIdentifierComposite) {
1167
            return $fieldName === $this->identifier[0];
1168
        }
1169
1170
        return in_array($fieldName, $this->identifier, true);
1171
    }
1172
1173
    /**
1174
     * Checks if the field is unique.
1175
     *
1176
     * @param string $fieldName The field name.
1177
     *
1178
     * @return boolean TRUE if the field is unique, FALSE otherwise.
1179
     */
1180
    public function isUniqueField($fieldName)
1181
    {
1182
        $mapping = $this->getFieldMapping($fieldName);
1183
1184
        return false !== $mapping && isset($mapping['unique']) && $mapping['unique'];
1185
    }
1186
1187
    /**
1188
     * Checks if the field is not null.
1189
     *
1190
     * @param string $fieldName The field name.
1191
     *
1192
     * @return boolean TRUE if the field is not null, FALSE otherwise.
1193
     */
1194
    public function isNullable($fieldName)
1195
    {
1196
        $mapping = $this->getFieldMapping($fieldName);
1197
1198
        return false !== $mapping && isset($mapping['nullable']) && $mapping['nullable'];
1199
    }
1200
1201
    /**
1202
     * Gets a column name for a field name.
1203
     * If the column name for the field cannot be found, the given field name
1204
     * is returned.
1205
     *
1206
     * @param string $fieldName The field name.
1207
     *
1208
     * @return string The column name.
1209
     */
1210
    public function getColumnName($fieldName)
1211
    {
1212
        return isset($this->columnNames[$fieldName])
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\Cla...adataInfo::$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

1212
        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...
1213
            ? $this->columnNames[$fieldName]
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\Cla...adataInfo::$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

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

1403
        /** @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...
1404
1405
        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...
1406
            throw MappingException::duplicateColumnName($this->name, $mapping['columnName']);
1407
        }
1408
1409
        $this->fieldNames[$mapping['columnName']] = $mapping['fieldName'];
1410
1411
        // Complete id mapping
1412
        if (isset($mapping['id']) && true === $mapping['id']) {
1413
            if ($this->versionField == $mapping['fieldName']) {
1414
                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
1415
            }
1416
1417
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1418
                $this->identifier[] = $mapping['fieldName'];
1419
            }
1420
1421
            // Check for composite key
1422 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...
1423
                $this->isIdentifierComposite = true;
1424
            }
1425
        }
1426
1427
        if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
1428 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...
1429
                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
1430
            }
1431
1432
            $mapping['requireSQLConversion'] = true;
1433
        }
1434
    }
1435
1436
    /**
1437
     * Validates & completes the basic mapping information that is common to all
1438
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
1439
     *
1440
     * @param array $mapping The mapping.
1441
     *
1442
     * @return array The updated mapping.
1443
     *
1444
     * @throws MappingException If something is wrong with the mapping.
1445
     */
1446
    protected function _validateAndCompleteAssociationMapping(array $mapping)
1447
    {
1448
        if ( ! isset($mapping['mappedBy'])) {
1449
            $mapping['mappedBy'] = null;
1450
        }
1451
1452
        if ( ! isset($mapping['inversedBy'])) {
1453
            $mapping['inversedBy'] = null;
1454
        }
1455
1456
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
1457
1458
        if (empty($mapping['indexBy'])) {
1459
            unset($mapping['indexBy']);
1460
        }
1461
1462
        // If targetEntity is unqualified, assume it is in the same namespace as
1463
        // the sourceEntity.
1464
        $mapping['sourceEntity'] = $this->name;
1465
1466
        if (isset($mapping['targetEntity'])) {
1467
            $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

1467
            $mapping['targetEntity'] = $this->fullyQualifiedClassName(/** @scrutinizer ignore-type */ $mapping['targetEntity']);
Loading history...
1468
            $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
1469
        }
1470
1471 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...
1472
            throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
1473
        }
1474
1475
        // Complete id mapping
1476
        if (isset($mapping['id']) && true === $mapping['id']) {
1477
            if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1478
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
1479
            }
1480
1481
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1482
                if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) {
1483
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
1484
                        $mapping['targetEntity'], $this->name, $mapping['fieldName']
1485
                    );
1486
                }
1487
1488
                $this->identifier[] = $mapping['fieldName'];
1489
                $this->containsForeignIdentifier = true;
1490
            }
1491
1492
            // Check for composite key
1493 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...
1494
                $this->isIdentifierComposite = true;
1495
            }
1496
1497
            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...
1498
                throw CacheException::nonCacheableEntityAssociation($this->name, $mapping['fieldName']);
1499
            }
1500
        }
1501
1502
        // Mandatory attributes for both sides
1503
        // Mandatory: fieldName, targetEntity
1504 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...
1505
            throw MappingException::missingFieldName($this->name);
1506
        }
1507
1508
        if ( ! isset($mapping['targetEntity'])) {
1509
            throw MappingException::missingTargetEntity($mapping['fieldName']);
1510
        }
1511
1512
        // Mandatory and optional attributes for either side
1513
        if ( ! $mapping['mappedBy']) {
1514
            if (isset($mapping['joinTable']) && $mapping['joinTable']) {
1515
                if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
1516
                    $mapping['joinTable']['name']   = trim($mapping['joinTable']['name'], '`');
1517
                    $mapping['joinTable']['quoted'] = true;
1518
                }
1519
            }
1520
        } else {
1521
            $mapping['isOwningSide'] = false;
1522
        }
1523
1524 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...
1525
            throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']);
1526
        }
1527
1528
        // Fetch mode. Default fetch mode to LAZY, if not set.
1529
        if ( ! isset($mapping['fetch'])) {
1530
            $mapping['fetch'] = self::FETCH_LAZY;
1531
        }
1532
1533
        // Cascades
1534
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
1535
1536
        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1537
        if (in_array('all', $cascades)) {
1538
            $cascades = $allCascades;
1539
        } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
1540
            throw MappingException::invalidCascadeOption(
1541
                array_diff($cascades, $allCascades),
1542
                $this->name,
1543
                $mapping['fieldName']
1544
            );
1545
        }
1546
1547
        $mapping['cascade'] = $cascades;
1548
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1549
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1550
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1551
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1552
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1553
1554
        return $mapping;
1555
    }
1556
1557
    /**
1558
     * Validates & completes a one-to-one association mapping.
1559
     *
1560
     * @param array $mapping The mapping to validate & complete.
1561
     *
1562
     * @return array The validated & completed mapping.
1563
     *
1564
     * @throws RuntimeException
1565
     * @throws MappingException
1566
     */
1567
    protected function _validateAndCompleteOneToOneMapping(array $mapping)
1568
    {
1569
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1570
1571
        if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
1572
            $mapping['isOwningSide'] = true;
1573
        }
1574
1575
        if ($mapping['isOwningSide']) {
1576
            if (empty($mapping['joinColumns'])) {
1577
                // Apply default join column
1578
                $mapping['joinColumns'] = [
1579
                    [
1580
                        'name' => $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name),
1581
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName()
1582
                    ]
1583
                ];
1584
            }
1585
1586
            $uniqueConstraintColumns = [];
1587
1588
            foreach ($mapping['joinColumns'] as &$joinColumn) {
1589
                if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
1590
                    if (count($mapping['joinColumns']) === 1) {
1591
                        if (empty($mapping['id'])) {
1592
                            $joinColumn['unique'] = true;
1593
                        }
1594
                    } else {
1595
                        $uniqueConstraintColumns[] = $joinColumn['name'];
1596
                    }
1597
                }
1598
1599
                if (empty($joinColumn['name'])) {
1600
                    $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name);
1601
                }
1602
1603
                if (empty($joinColumn['referencedColumnName'])) {
1604
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1605
                }
1606
1607
                if ($joinColumn['name'][0] === '`') {
1608
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1609
                    $joinColumn['quoted'] = true;
1610
                }
1611
1612
                if ($joinColumn['referencedColumnName'][0] === '`') {
1613
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1614
                    $joinColumn['quoted']               = true;
1615
                }
1616
1617
                $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1618
                $mapping['joinColumnFieldNames'][$joinColumn['name']] = $joinColumn['fieldName'] ?? $joinColumn['name'];
1619
            }
1620
1621
            if ($uniqueConstraintColumns) {
1622
                if ( ! $this->table) {
1623
                    throw new RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.");
1624
                }
1625
1626
                $this->table['uniqueConstraints'][$mapping['fieldName'] . "_uniq"] = [
1627
                    'columns' => $uniqueConstraintColumns
1628
                ];
1629
            }
1630
1631
            $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
1632
        }
1633
1634
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1635
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1636
1637
        if ($mapping['orphanRemoval']) {
1638
            unset($mapping['unique']);
1639
        }
1640
1641 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...
1642
            throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
1643
        }
1644
1645
        return $mapping;
1646
    }
1647
1648
    /**
1649
     * Validates & completes a one-to-many association mapping.
1650
     *
1651
     * @param array $mapping The mapping to validate and complete.
1652
     *
1653
     * @return array The validated and completed mapping.
1654
     *
1655
     * @throws MappingException
1656
     * @throws InvalidArgumentException
1657
     */
1658
    protected function _validateAndCompleteOneToManyMapping(array $mapping)
1659
    {
1660
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1661
1662
        // OneToMany-side MUST be inverse (must have mappedBy)
1663
        if ( ! isset($mapping['mappedBy'])) {
1664
            throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
1665
        }
1666
1667
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1668
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1669
1670
        $this->assertMappingOrderBy($mapping);
1671
1672
        return $mapping;
1673
    }
1674
1675
    /**
1676
     * Validates & completes a many-to-many association mapping.
1677
     *
1678
     * @param array $mapping The mapping to validate & complete.
1679
     *
1680
     * @return array The validated & completed mapping.
1681
     *
1682
     * @throws \InvalidArgumentException
1683
     */
1684
    protected function _validateAndCompleteManyToManyMapping(array $mapping)
1685
    {
1686
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1687
1688
        if ($mapping['isOwningSide']) {
1689
            // owning side MUST have a join table
1690
            if ( ! isset($mapping['joinTable']['name'])) {
1691
                $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
1692
            }
1693
1694
            $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
1695
                && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
1696
1697 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...
1698
                $mapping['joinTable']['joinColumns'] = [
1699
                    [
1700
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
1701
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1702
                        'onDelete' => 'CASCADE'
1703
                    ]
1704
                ];
1705
            }
1706
1707 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...
1708
                $mapping['joinTable']['inverseJoinColumns'] = [
1709
                    [
1710
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
1711
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1712
                        'onDelete' => 'CASCADE'
1713
                    ]
1714
                ];
1715
            }
1716
1717
            $mapping['joinTableColumns'] = [];
1718
1719 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...
1720
                if (empty($joinColumn['name'])) {
1721
                    $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
1722
                }
1723
1724
                if (empty($joinColumn['referencedColumnName'])) {
1725
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1726
                }
1727
1728
                if ($joinColumn['name'][0] === '`') {
1729
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1730
                    $joinColumn['quoted'] = true;
1731
                }
1732
1733
                if ($joinColumn['referencedColumnName'][0] === '`') {
1734
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1735
                    $joinColumn['quoted']               = true;
1736
                }
1737
1738
                if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') {
1739
                    $mapping['isOnDeleteCascade'] = true;
1740
                }
1741
1742
                $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1743
                $mapping['joinTableColumns'][] = $joinColumn['name'];
1744
            }
1745
1746 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...
1747
                if (empty($inverseJoinColumn['name'])) {
1748
                    $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']);
1749
                }
1750
1751
                if (empty($inverseJoinColumn['referencedColumnName'])) {
1752
                    $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1753
                }
1754
1755
                if ($inverseJoinColumn['name'][0] === '`') {
1756
                    $inverseJoinColumn['name']   = trim($inverseJoinColumn['name'], '`');
1757
                    $inverseJoinColumn['quoted'] = true;
1758
                }
1759
1760
                if ($inverseJoinColumn['referencedColumnName'][0] === '`') {
1761
                    $inverseJoinColumn['referencedColumnName']  = trim($inverseJoinColumn['referencedColumnName'], '`');
1762
                    $inverseJoinColumn['quoted']                = true;
1763
                }
1764
1765
                if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
1766
                    $mapping['isOnDeleteCascade'] = true;
1767
                }
1768
1769
                $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
1770
                $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
1771
            }
1772
        }
1773
1774
        $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1775
1776
        $this->assertMappingOrderBy($mapping);
1777
1778
        return $mapping;
1779
    }
1780
1781
    /**
1782
     * {@inheritDoc}
1783
     */
1784
    public function getIdentifierFieldNames()
1785
    {
1786
        return $this->identifier;
1787
    }
1788
1789
    /**
1790
     * Gets the name of the single id field. Note that this only works on
1791
     * entity classes that have a single-field pk.
1792
     *
1793
     * @return string
1794
     *
1795
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1796
     */
1797
    public function getSingleIdentifierFieldName()
1798
    {
1799
        if ($this->isIdentifierComposite) {
1800
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
1801
        }
1802
1803
        if ( ! isset($this->identifier[0])) {
1804
            throw MappingException::noIdDefined($this->name);
1805
        }
1806
1807
        return $this->identifier[0];
1808
    }
1809
1810
    /**
1811
     * Gets the column name of the single id column. Note that this only works on
1812
     * entity classes that have a single-field pk.
1813
     *
1814
     * @return string
1815
     *
1816
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1817
     */
1818
    public function getSingleIdentifierColumnName()
1819
    {
1820
        return $this->getColumnName($this->getSingleIdentifierFieldName());
1821
    }
1822
1823
    /**
1824
     * INTERNAL:
1825
     * Sets the mapped identifier/primary key fields of this class.
1826
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
1827
     *
1828
     * @param array $identifier
1829
     *
1830
     * @return void
1831
     */
1832
    public function setIdentifier(array $identifier)
1833
    {
1834
        $this->identifier = $identifier;
1835
        $this->isIdentifierComposite = (count($this->identifier) > 1);
1836
    }
1837
1838
    /**
1839
     * {@inheritDoc}
1840
     */
1841
    public function getIdentifier()
1842
    {
1843
        return $this->identifier;
1844
    }
1845
1846
    /**
1847
     * {@inheritDoc}
1848
     */
1849
    public function hasField($fieldName)
1850
    {
1851
        return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
1852
    }
1853
1854
    /**
1855
     * Gets an array containing all the column names.
1856
     *
1857
     * @param array|null $fieldNames
1858
     *
1859
     * @return array
1860
     */
1861
    public function getColumnNames(array $fieldNames = null)
1862
    {
1863
        if (null === $fieldNames) {
1864
            return array_keys($this->fieldNames);
1865
        }
1866
1867
        return array_values(array_map([$this, 'getColumnName'], $fieldNames));
1868
    }
1869
1870
    /**
1871
     * Returns an array with all the identifier column names.
1872
     *
1873
     * @return array
1874
     */
1875
    public function getIdentifierColumnNames()
1876
    {
1877
        $columnNames = [];
1878
1879
        foreach ($this->identifier as $idProperty) {
1880
            if (isset($this->fieldMappings[$idProperty])) {
1881
                $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
1882
1883
                continue;
1884
            }
1885
1886
            // Association defined as Id field
1887
            $joinColumns      = $this->associationMappings[$idProperty]['joinColumns'];
1888
            $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
1889
1890
            $columnNames = array_merge($columnNames, $assocColumnNames);
1891
        }
1892
1893
        return $columnNames;
1894
    }
1895
1896
    /**
1897
     * Sets the type of Id generator to use for the mapped class.
1898
     *
1899
     * @param int $generatorType
1900
     *
1901
     * @return void
1902
     */
1903
    public function setIdGeneratorType($generatorType)
1904
    {
1905
        $this->generatorType = $generatorType;
1906
    }
1907
1908
    /**
1909
     * Checks whether the mapped class uses an Id generator.
1910
     *
1911
     * @return boolean TRUE if the mapped class uses an Id generator, FALSE otherwise.
1912
     */
1913
    public function usesIdGenerator()
1914
    {
1915
        return $this->generatorType != self::GENERATOR_TYPE_NONE;
1916
    }
1917
1918
    /**
1919
     * @return boolean
1920
     */
1921
    public function isInheritanceTypeNone()
1922
    {
1923
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1924
    }
1925
1926
    /**
1927
     * Checks whether the mapped class uses the JOINED inheritance mapping strategy.
1928
     *
1929
     * @return boolean TRUE if the class participates in a JOINED inheritance mapping,
1930
     *                 FALSE otherwise.
1931
     */
1932
    public function isInheritanceTypeJoined()
1933
    {
1934
        return $this->inheritanceType == self::INHERITANCE_TYPE_JOINED;
1935
    }
1936
1937
    /**
1938
     * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy.
1939
     *
1940
     * @return boolean TRUE if the class participates in a SINGLE_TABLE inheritance mapping,
1941
     *                 FALSE otherwise.
1942
     */
1943
    public function isInheritanceTypeSingleTable()
1944
    {
1945
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE;
1946
    }
1947
1948
    /**
1949
     * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy.
1950
     *
1951
     * @return boolean TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping,
1952
     *                 FALSE otherwise.
1953
     */
1954
    public function isInheritanceTypeTablePerClass()
1955
    {
1956
        return $this->inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
1957
    }
1958
1959
    /**
1960
     * Checks whether the class uses an identity column for the Id generation.
1961
     *
1962
     * @return boolean TRUE if the class uses the IDENTITY generator, FALSE otherwise.
1963
     */
1964
    public function isIdGeneratorIdentity()
1965
    {
1966
        return $this->generatorType == self::GENERATOR_TYPE_IDENTITY;
1967
    }
1968
1969
    /**
1970
     * Checks whether the class uses a sequence for id generation.
1971
     *
1972
     * @return boolean TRUE if the class uses the SEQUENCE generator, FALSE otherwise.
1973
     */
1974
    public function isIdGeneratorSequence()
1975
    {
1976
        return $this->generatorType == self::GENERATOR_TYPE_SEQUENCE;
1977
    }
1978
1979
    /**
1980
     * Checks whether the class uses a table for id generation.
1981
     *
1982
     * @return boolean TRUE if the class uses the TABLE generator, FALSE otherwise.
1983
     */
1984
    public function isIdGeneratorTable()
1985
    {
1986
        return $this->generatorType == self::GENERATOR_TYPE_TABLE;
1987
    }
1988
1989
    /**
1990
     * Checks whether the class has a natural identifier/pk (which means it does
1991
     * not use any Id generator.
1992
     *
1993
     * @return boolean
1994
     */
1995
    public function isIdentifierNatural()
1996
    {
1997
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
1998
    }
1999
2000
    /**
2001
     * Checks whether the class use a UUID for id generation.
2002
     *
2003
     * @return boolean
2004
     */
2005
    public function isIdentifierUuid()
2006
    {
2007
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
2008
    }
2009
2010
    /**
2011
     * Gets the type of a field.
2012
     *
2013
     * @param string $fieldName
2014
     *
2015
     * @return \Doctrine\DBAL\Types\Type|string|null
2016
     *
2017
     * @todo 3.0 Remove this. PersisterHelper should fix it somehow
2018
     */
2019
    public function getTypeOfField($fieldName)
2020
    {
2021
        return isset($this->fieldMappings[$fieldName])
2022
            ? $this->fieldMappings[$fieldName]['type']
2023
            : null;
2024
    }
2025
2026
    /**
2027
     * Gets the type of a column.
2028
     *
2029
     * @param string $columnName
2030
     *
2031
     * @return \Doctrine\DBAL\Types\Type|string|null
2032
     *
2033
     * @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
2034
     *             that is derived by a referenced field on a different entity.
2035
     */
2036
    public function getTypeOfColumn($columnName)
2037
    {
2038
        return $this->getTypeOfField($this->getFieldName($columnName));
2039
    }
2040
2041
    /**
2042
     * Gets the name of the primary table.
2043
     *
2044
     * @return string
2045
     */
2046
    public function getTableName()
2047
    {
2048
        return $this->table['name'];
2049
    }
2050
2051
    /**
2052
     * Gets primary table's schema name.
2053
     *
2054
     * @return string|null
2055
     */
2056
    public function getSchemaName()
2057
    {
2058
        return $this->table['schema'] ?? null;
2059
    }
2060
2061
    /**
2062
     * Gets the table name to use for temporary identifier tables of this class.
2063
     *
2064
     * @return string
2065
     */
2066
    public function getTemporaryIdTableName()
2067
    {
2068
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
2069
        return str_replace('.', '_', $this->getTableName() . '_id_tmp');
2070
    }
2071
2072
    /**
2073
     * Sets the mapped subclasses of this class.
2074
     *
2075
     * @param array $subclasses The names of all mapped subclasses.
2076
     *
2077
     * @return void
2078
     */
2079
    public function setSubclasses(array $subclasses)
2080
    {
2081
        foreach ($subclasses as $subclass) {
2082
            $this->subClasses[] = $this->fullyQualifiedClassName($subclass);
2083
        }
2084
    }
2085
2086
    /**
2087
     * Sets the parent class names.
2088
     * Assumes that the class names in the passed array are in the order:
2089
     * directParent -> directParentParent -> directParentParentParent ... -> root.
2090
     *
2091
     * @param array $classNames
2092
     *
2093
     * @return void
2094
     */
2095
    public function setParentClasses(array $classNames)
2096
    {
2097
        $this->parentClasses = $classNames;
2098
2099
        if (count($classNames) > 0) {
2100
            $this->rootEntityName = array_pop($classNames);
2101
        }
2102
    }
2103
2104
    /**
2105
     * Sets the inheritance type used by the class and its subclasses.
2106
     *
2107
     * @param integer $type
2108
     *
2109
     * @return void
2110
     *
2111
     * @throws MappingException
2112
     */
2113
    public function setInheritanceType($type)
2114
    {
2115
        if ( ! $this->_isInheritanceType($type)) {
2116
            throw MappingException::invalidInheritanceType($this->name, $type);
2117
        }
2118
2119
        $this->inheritanceType = $type;
2120
    }
2121
2122
    /**
2123
     * Sets the association to override association mapping of property for an entity relationship.
2124
     *
2125
     * @param string $fieldName
2126
     * @param array  $overrideMapping
2127
     *
2128
     * @return void
2129
     *
2130
     * @throws MappingException
2131
     */
2132
    public function setAssociationOverride($fieldName, array $overrideMapping)
2133
    {
2134
        if ( ! isset($this->associationMappings[$fieldName])) {
2135
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2136
        }
2137
2138
        $mapping = $this->associationMappings[$fieldName];
2139
2140
        if (isset($overrideMapping['joinColumns'])) {
2141
            $mapping['joinColumns'] = $overrideMapping['joinColumns'];
2142
        }
2143
2144
        if (isset($overrideMapping['inversedBy'])) {
2145
            $mapping['inversedBy'] = $overrideMapping['inversedBy'];
2146
        }
2147
2148
        if (isset($overrideMapping['joinTable'])) {
2149
            $mapping['joinTable'] = $overrideMapping['joinTable'];
2150
        }
2151
2152
        if (isset($overrideMapping['fetch'])) {
2153
            $mapping['fetch'] = $overrideMapping['fetch'];
2154
        }
2155
2156
        $mapping['joinColumnFieldNames']        = null;
2157
        $mapping['joinTableColumns']            = null;
2158
        $mapping['sourceToTargetKeyColumns']    = null;
2159
        $mapping['relationToSourceKeyColumns']  = null;
2160
        $mapping['relationToTargetKeyColumns']  = null;
2161
2162
        switch ($mapping['type']) {
2163
            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...
2164
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2165
                break;
2166
            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...
2167
                $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2168
                break;
2169
            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...
2170
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2171
                break;
2172
            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...
2173
                $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2174
                break;
2175
        }
2176
2177
        $this->associationMappings[$fieldName] = $mapping;
2178
    }
2179
2180
    /**
2181
     * Sets the override for a mapped field.
2182
     *
2183
     * @param string $fieldName
2184
     * @param array  $overrideMapping
2185
     *
2186
     * @return void
2187
     *
2188
     * @throws MappingException
2189
     */
2190
    public function setAttributeOverride($fieldName, array $overrideMapping)
2191
    {
2192
        if ( ! isset($this->fieldMappings[$fieldName])) {
2193
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2194
        }
2195
2196
        $mapping = $this->fieldMappings[$fieldName];
2197
2198
        if (isset($mapping['id'])) {
2199
            $overrideMapping['id'] = $mapping['id'];
2200
        }
2201
2202
        if ( ! isset($overrideMapping['type'])) {
2203
            $overrideMapping['type'] = $mapping['type'];
2204
        }
2205
2206
        if ( ! isset($overrideMapping['fieldName'])) {
2207
            $overrideMapping['fieldName'] = $mapping['fieldName'];
2208
        }
2209
2210
        if ($overrideMapping['type'] !== $mapping['type']) {
2211
            throw MappingException::invalidOverrideFieldType($this->name, $fieldName);
2212
        }
2213
2214
        unset($this->fieldMappings[$fieldName]);
2215
        unset($this->fieldNames[$mapping['columnName']]);
2216
        unset($this->columnNames[$mapping['fieldName']]);
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\Cla...adataInfo::$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

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

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

2373
            throw MappingException::/** @scrutinizer ignore-call */ duplicateAssociationMapping($this->name, $mapping['fieldName']);

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...
2374
        }
2375
        $this->associationMappings[$mapping['fieldName']] = $mapping;
2376
    }
2377
2378
    /**
2379
     * INTERNAL:
2380
     * Adds a field mapping without completing/validating it.
2381
     * This is mainly used to add inherited field mappings to derived classes.
2382
     *
2383
     * @param array $fieldMapping
2384
     *
2385
     * @return void
2386
     */
2387
    public function addInheritedFieldMapping(array $fieldMapping)
2388
    {
2389
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
2390
        $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\Cla...adataInfo::$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

2390
        /** @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...
2391
        $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
2392
    }
2393
2394
    /**
2395
     * INTERNAL:
2396
     * Adds a named query to this class.
2397
     *
2398
     * @param array $queryMapping
2399
     *
2400
     * @return void
2401
     *
2402
     * @throws MappingException
2403
     */
2404
    public function addNamedQuery(array $queryMapping)
2405
    {
2406
        if (!isset($queryMapping['name'])) {
2407
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
0 ignored issues
show
Bug introduced by
The method nameIsMandatoryForQueryMapping() does not exist on Doctrine\ORM\Mapping\MappingException. Did you maybe mean nameIsMandatoryForSqlResultSetMapping()? ( Ignorable by Annotation )

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

2407
            throw MappingException::/** @scrutinizer ignore-call */ nameIsMandatoryForQueryMapping($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...
2408
        }
2409
2410 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...
2411
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2412
        }
2413
2414
        if (!isset($queryMapping['query'])) {
2415
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
0 ignored issues
show
Bug introduced by
The method emptyQueryMapping() does not exist on Doctrine\ORM\Mapping\MappingException. ( Ignorable by Annotation )

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

2415
            throw MappingException::/** @scrutinizer ignore-call */ emptyQueryMapping($this->name, $queryMapping['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...
2416
        }
2417
2418
        $name   = $queryMapping['name'];
2419
        $query  = $queryMapping['query'];
2420
        $dql    = str_replace('__CLASS__', $this->name, $query);
2421
2422
        $this->namedQueries[$name] = [
2423
            'name'  => $name,
2424
            'query' => $query,
2425
            'dql'   => $dql,
2426
        ];
2427
    }
2428
2429
    /**
2430
     * INTERNAL:
2431
     * Adds a named native query to this class.
2432
     *
2433
     * @param array $queryMapping
2434
     *
2435
     * @return void
2436
     *
2437
     * @throws MappingException
2438
     */
2439
    public function addNamedNativeQuery(array $queryMapping)
2440
    {
2441
        if (!isset($queryMapping['name'])) {
2442
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2443
        }
2444
2445 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...
2446
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2447
        }
2448
2449
        if (!isset($queryMapping['query'])) {
2450
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2451
        }
2452
2453
        if (!isset($queryMapping['resultClass']) && !isset($queryMapping['resultSetMapping'])) {
2454
            throw MappingException::missingQueryMapping($this->name, $queryMapping['name']);
2455
        }
2456
2457
        $queryMapping['isSelfClass'] = false;
2458
2459
        if (isset($queryMapping['resultClass'])) {
2460
            if ($queryMapping['resultClass'] === '__CLASS__') {
2461
2462
                $queryMapping['isSelfClass'] = true;
2463
                $queryMapping['resultClass'] = $this->name;
2464
            }
2465
2466
            $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
2467
            $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\');
2468
        }
2469
2470
        $this->namedNativeQueries[$queryMapping['name']] = $queryMapping;
2471
    }
2472
2473
    /**
2474
     * INTERNAL:
2475
     * Adds a sql result set mapping to this class.
2476
     *
2477
     * @param array $resultMapping
2478
     *
2479
     * @return void
2480
     *
2481
     * @throws MappingException
2482
     */
2483
    public function addSqlResultSetMapping(array $resultMapping)
2484
    {
2485
        if (!isset($resultMapping['name'])) {
2486
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name);
2487
        }
2488
2489
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
2490
            throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']);
2491
        }
2492
2493
        if (isset($resultMapping['entities'])) {
2494
            foreach ($resultMapping['entities'] as $key => $entityResult) {
2495
                if (!isset($entityResult['entityClass'])) {
2496
                    throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']);
2497
                }
2498
2499
                $entityResult['isSelfClass'] = false;
2500
                if ($entityResult['entityClass'] === '__CLASS__') {
2501
2502
                    $entityResult['isSelfClass'] = true;
2503
                    $entityResult['entityClass'] = $this->name;
2504
2505
                }
2506
2507
                $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']);
2508
2509
                $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
2510
                $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
2511
2512
                if (isset($entityResult['fields'])) {
2513
                    foreach ($entityResult['fields'] as $k => $field) {
2514
                        if (!isset($field['name'])) {
2515
                            throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']);
2516
                        }
2517
2518
                        if (!isset($field['column'])) {
2519
                            $fieldName = $field['name'];
2520
                            if (strpos($fieldName, '.')) {
2521
                                list(, $fieldName) = explode('.', $fieldName);
2522
                            }
2523
2524
                            $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
2525
                        }
2526
                    }
2527
                }
2528
            }
2529
        }
2530
2531
        $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
2532
    }
2533
2534
    /**
2535
     * Adds a one-to-one mapping.
2536
     *
2537
     * @param array $mapping The mapping.
2538
     *
2539
     * @return void
2540
     */
2541
    public function mapOneToOne(array $mapping)
2542
    {
2543
        $mapping['type'] = self::ONE_TO_ONE;
2544
2545
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2546
2547
        $this->_storeAssociationMapping($mapping);
2548
    }
2549
2550
    /**
2551
     * Adds a one-to-many mapping.
2552
     *
2553
     * @param array $mapping The mapping.
2554
     *
2555
     * @return void
2556
     */
2557
    public function mapOneToMany(array $mapping)
2558
    {
2559
        $mapping['type'] = self::ONE_TO_MANY;
2560
2561
        $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2562
2563
        $this->_storeAssociationMapping($mapping);
2564
    }
2565
2566
    /**
2567
     * Adds a many-to-one mapping.
2568
     *
2569
     * @param array $mapping The mapping.
2570
     *
2571
     * @return void
2572
     */
2573
    public function mapManyToOne(array $mapping)
2574
    {
2575
        $mapping['type'] = self::MANY_TO_ONE;
2576
2577
        // A many-to-one mapping is essentially a one-one backreference
2578
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2579
2580
        $this->_storeAssociationMapping($mapping);
2581
    }
2582
2583
    /**
2584
     * Adds a many-to-many mapping.
2585
     *
2586
     * @param array $mapping The mapping.
2587
     *
2588
     * @return void
2589
     */
2590
    public function mapManyToMany(array $mapping)
2591
    {
2592
        $mapping['type'] = self::MANY_TO_MANY;
2593
2594
        $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2595
2596
        $this->_storeAssociationMapping($mapping);
2597
    }
2598
2599
    /**
2600
     * Stores the association mapping.
2601
     *
2602
     * @param array $assocMapping
2603
     *
2604
     * @return void
2605
     *
2606
     * @throws MappingException
2607
     */
2608
    protected function _storeAssociationMapping(array $assocMapping)
2609
    {
2610
        $sourceFieldName = $assocMapping['fieldName'];
2611
2612
        $this->assertFieldNotMapped($sourceFieldName);
2613
2614
        $this->associationMappings[$sourceFieldName] = $assocMapping;
2615
    }
2616
2617
    /**
2618
     * Registers a custom repository class for the entity class.
2619
     *
2620
     * @param string $repositoryClassName The class name of the custom mapper.
2621
     *
2622
     * @return void
2623
     */
2624
    public function setCustomRepositoryClass($repositoryClassName)
2625
    {
2626
        $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
2627
    }
2628
2629
    /**
2630
     * Dispatches the lifecycle event of the given entity to the registered
2631
     * lifecycle callbacks and lifecycle listeners.
2632
     *
2633
     * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker
2634
     *
2635
     * @param string $lifecycleEvent The lifecycle event.
2636
     * @param object $entity         The Entity on which the event occurred.
2637
     *
2638
     * @return void
2639
     */
2640
    public function invokeLifecycleCallbacks($lifecycleEvent, $entity)
2641
    {
2642
        foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) {
2643
            $entity->$callback();
2644
        }
2645
    }
2646
2647
    /**
2648
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
2649
     *
2650
     * @param string $lifecycleEvent
2651
     *
2652
     * @return boolean
2653
     */
2654
    public function hasLifecycleCallbacks($lifecycleEvent)
2655
    {
2656
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
2657
    }
2658
2659
    /**
2660
     * Gets the registered lifecycle callbacks for an event.
2661
     *
2662
     * @param string $event
2663
     *
2664
     * @return array
2665
     */
2666
    public function getLifecycleCallbacks($event)
2667
    {
2668
        return $this->lifecycleCallbacks[$event] ?? [];
2669
    }
2670
2671
    /**
2672
     * Adds a lifecycle callback for entities of this class.
2673
     *
2674
     * @param string $callback
2675
     * @param string $event
2676
     *
2677
     * @return void
2678
     */
2679
    public function addLifecycleCallback($callback, $event)
2680
    {
2681
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
2682
            return;
2683
        }
2684
2685
        $this->lifecycleCallbacks[$event][] = $callback;
2686
    }
2687
2688
    /**
2689
     * Sets the lifecycle callbacks for entities of this class.
2690
     * Any previously registered callbacks are overwritten.
2691
     *
2692
     * @param array $callbacks
2693
     *
2694
     * @return void
2695
     */
2696
    public function setLifecycleCallbacks(array $callbacks)
2697
    {
2698
        $this->lifecycleCallbacks = $callbacks;
2699
    }
2700
2701
    /**
2702
     * Adds a entity listener for entities of this class.
2703
     *
2704
     * @param string $eventName The entity lifecycle event.
2705
     * @param string $class     The listener class.
2706
     * @param string $method    The listener callback method.
2707
     *
2708
     * @throws \Doctrine\ORM\Mapping\MappingException
2709
     */
2710
    public function addEntityListener($eventName, $class, $method)
2711
    {
2712
        $class    = $this->fullyQualifiedClassName($class);
2713
2714
        $listener = [
2715
            'class'  => $class,
2716
            'method' => $method,
2717
        ];
2718
2719
        if ( ! class_exists($class)) {
2720
            throw MappingException::entityListenerClassNotFound($class, $this->name);
2721
        }
2722
2723
        if ( ! method_exists($class, $method)) {
2724
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->name);
2725
        }
2726
2727
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName])) {
2728
            throw MappingException::duplicateEntityListener($class, $method, $this->name);
2729
        }
2730
2731
        $this->entityListeners[$eventName][] = $listener;
2732
    }
2733
2734
    /**
2735
     * Sets the discriminator column definition.
2736
     *
2737
     * @param array $columnDef
2738
     *
2739
     * @return void
2740
     *
2741
     * @throws MappingException
2742
     *
2743
     * @see getDiscriminatorColumn()
2744
     */
2745
    public function setDiscriminatorColumn($columnDef)
2746
    {
2747
        if ($columnDef !== null) {
2748
            if ( ! isset($columnDef['name'])) {
2749
                throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name);
2750
            }
2751
2752
            if (isset($this->fieldNames[$columnDef['name']])) {
2753
                throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
2754
            }
2755
2756
            if ( ! isset($columnDef['fieldName'])) {
2757
                $columnDef['fieldName'] = $columnDef['name'];
2758
            }
2759
2760
            if ( ! isset($columnDef['type'])) {
2761
                $columnDef['type'] = "string";
2762
            }
2763
2764
            if (in_array($columnDef['type'], ["boolean", "array", "object", "datetime", "time", "date"])) {
2765
                throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\ORM\Mapping\Map...scriminatorColumnType() has too many arguments starting with $columnDef['type']. ( Ignorable by Annotation )

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

2765
                throw MappingException::/** @scrutinizer ignore-call */ invalidDiscriminatorColumnType($this->name, $columnDef['type']);

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...
2766
            }
2767
2768
            $this->discriminatorColumn = $columnDef;
2769
        }
2770
    }
2771
2772
    /**
2773
     * Sets the discriminator values used by this class.
2774
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
2775
     *
2776
     * @param array $map
2777
     *
2778
     * @return void
2779
     */
2780
    public function setDiscriminatorMap(array $map)
2781
    {
2782
        foreach ($map as $value => $className) {
2783
            $this->addDiscriminatorMapClass($value, $className);
2784
        }
2785
    }
2786
2787
    /**
2788
     * Adds one entry of the discriminator map with a new class and corresponding name.
2789
     *
2790
     * @param string $name
2791
     * @param string $className
2792
     *
2793
     * @return void
2794
     *
2795
     * @throws MappingException
2796
     */
2797
    public function addDiscriminatorMapClass($name, $className)
2798
    {
2799
        $className = $this->fullyQualifiedClassName($className);
2800
        $className = ltrim($className, '\\');
2801
2802
        $this->discriminatorMap[$name] = $className;
2803
2804
        if ($this->name === $className) {
2805
            $this->discriminatorValue = $name;
2806
2807
            return;
2808
        }
2809
2810
        if ( ! (class_exists($className) || interface_exists($className))) {
2811
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
2812
        }
2813
2814
        if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) {
2815
            $this->subClasses[] = $className;
2816
        }
2817
    }
2818
2819
    /**
2820
     * Checks whether the class has a named query with the given query name.
2821
     *
2822
     * @param string $queryName
2823
     *
2824
     * @return boolean
2825
     */
2826
    public function hasNamedQuery($queryName)
2827
    {
2828
        return isset($this->namedQueries[$queryName]);
2829
    }
2830
2831
    /**
2832
     * Checks whether the class has a named native query with the given query name.
2833
     *
2834
     * @param string $queryName
2835
     *
2836
     * @return boolean
2837
     */
2838
    public function hasNamedNativeQuery($queryName)
2839
    {
2840
        return isset($this->namedNativeQueries[$queryName]);
2841
    }
2842
2843
    /**
2844
     * Checks whether the class has a named native query with the given query name.
2845
     *
2846
     * @param string $name
2847
     *
2848
     * @return boolean
2849
     */
2850
    public function hasSqlResultSetMapping($name)
2851
    {
2852
        return isset($this->sqlResultSetMappings[$name]);
2853
    }
2854
2855
    /**
2856
     * {@inheritDoc}
2857
     */
2858
    public function hasAssociation($fieldName)
2859
    {
2860
        return isset($this->associationMappings[$fieldName]);
2861
    }
2862
2863
    /**
2864
     * {@inheritDoc}
2865
     */
2866
    public function isSingleValuedAssociation($fieldName)
2867
    {
2868
        return isset($this->associationMappings[$fieldName])
2869
            && ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2870
    }
2871
2872
    /**
2873
     * {@inheritDoc}
2874
     */
2875
    public function isCollectionValuedAssociation($fieldName)
2876
    {
2877
        return isset($this->associationMappings[$fieldName])
2878
            && ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2879
    }
2880
2881
    /**
2882
     * Is this an association that only has a single join column?
2883
     *
2884
     * @param string $fieldName
2885
     *
2886
     * @return bool
2887
     */
2888
    public function isAssociationWithSingleJoinColumn($fieldName)
2889
    {
2890
        return isset($this->associationMappings[$fieldName])
2891
            && isset($this->associationMappings[$fieldName]['joinColumns'][0])
2892
            && ! isset($this->associationMappings[$fieldName]['joinColumns'][1]);
2893
    }
2894
2895
    /**
2896
     * Returns the single association join column (if any).
2897
     *
2898
     * @param string $fieldName
2899
     *
2900
     * @return string
2901
     *
2902
     * @throws MappingException
2903
     */
2904 View Code Duplication
    public function getSingleAssociationJoinColumnName($fieldName)
2905
    {
2906
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2907
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2908
        }
2909
2910
        return $this->associationMappings[$fieldName]['joinColumns'][0]['name'];
2911
    }
2912
2913
    /**
2914
     * Returns the single association referenced join column name (if any).
2915
     *
2916
     * @param string $fieldName
2917
     *
2918
     * @return string
2919
     *
2920
     * @throws MappingException
2921
     */
2922 View Code Duplication
    public function getSingleAssociationReferencedJoinColumnName($fieldName)
2923
    {
2924
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2925
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2926
        }
2927
2928
        return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName'];
2929
    }
2930
2931
    /**
2932
     * Used to retrieve a fieldname for either field or association from a given column.
2933
     *
2934
     * This method is used in foreign-key as primary-key contexts.
2935
     *
2936
     * @param string $columnName
2937
     *
2938
     * @return string
2939
     *
2940
     * @throws MappingException
2941
     */
2942
    public function getFieldForColumn($columnName)
2943
    {
2944
        if (isset($this->fieldNames[$columnName])) {
2945
            return $this->fieldNames[$columnName];
2946
        }
2947
2948
        foreach ($this->associationMappings as $assocName => $mapping) {
2949
            if ($this->isAssociationWithSingleJoinColumn($assocName) &&
2950
                $this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) {
2951
2952
                return $assocName;
2953
            }
2954
        }
2955
2956
        throw MappingException::noFieldNameFoundForColumn($this->name, $columnName);
2957
    }
2958
2959
    /**
2960
     * Sets the ID generator used to generate IDs for instances of this class.
2961
     *
2962
     * @param \Doctrine\ORM\Id\AbstractIdGenerator $generator
2963
     *
2964
     * @return void
2965
     */
2966
    public function setIdGenerator($generator)
2967
    {
2968
        $this->idGenerator = $generator;
2969
    }
2970
2971
    /**
2972
     * Sets definition.
2973
     *
2974
     * @param array $definition
2975
     *
2976
     * @return void
2977
     */
2978
    public function setCustomGeneratorDefinition(array $definition)
2979
    {
2980
        $this->customGeneratorDefinition = $definition;
2981
    }
2982
2983
    /**
2984
     * Sets the definition of the sequence ID generator for this class.
2985
     *
2986
     * The definition must have the following structure:
2987
     * <code>
2988
     * array(
2989
     *     'sequenceName'   => 'name',
2990
     *     'allocationSize' => 20,
2991
     *     'initialValue'   => 1
2992
     *     'quoted'         => 1
2993
     * )
2994
     * </code>
2995
     *
2996
     * @param array $definition
2997
     *
2998
     * @return void
2999
     *
3000
     * @throws MappingException
3001
     */
3002
    public function setSequenceGeneratorDefinition(array $definition)
3003
    {
3004
        if ( ! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') {
3005
            throw MappingException::missingSequenceName($this->name);
3006
        }
3007
3008
        if ($definition['sequenceName'][0] == '`') {
3009
            $definition['sequenceName']   = trim($definition['sequenceName'], '`');
3010
            $definition['quoted'] = true;
3011
        }
3012
3013
        if ( ! isset($definition['allocationSize']) || trim($definition['allocationSize']) === '') {
3014
            $definition['allocationSize'] = '1';
3015
        }
3016
3017
        if ( ! isset($definition['initialValue']) || trim($definition['initialValue']) === '') {
3018
            $definition['initialValue'] = '1';
3019
        }
3020
3021
        $this->sequenceGeneratorDefinition = $definition;
3022
    }
3023
3024
    /**
3025
     * Sets the version field mapping used for versioning. Sets the default
3026
     * value to use depending on the column type.
3027
     *
3028
     * @param array $mapping The version field mapping array.
3029
     *
3030
     * @return void
3031
     *
3032
     * @throws MappingException
3033
     */
3034
    public function setVersionMapping(array &$mapping)
3035
    {
3036
        $this->isVersioned = true;
3037
        $this->versionField = $mapping['fieldName'];
3038
3039
        if ( ! isset($mapping['default'])) {
3040
            if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'])) {
3041
                $mapping['default'] = 1;
3042
            } else if ($mapping['type'] == 'datetime') {
3043
                $mapping['default'] = 'CURRENT_TIMESTAMP';
3044
            } else {
3045
                throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']);
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\ORM\Mapping\Map...OptimisticLockingType() has too many arguments starting with $mapping['fieldName']. ( Ignorable by Annotation )

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

3045
                throw MappingException::/** @scrutinizer ignore-call */ unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']);

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...
3046
            }
3047
        }
3048
    }
3049
3050
    /**
3051
     * Sets whether this class is to be versioned for optimistic locking.
3052
     *
3053
     * @param boolean $bool
3054
     *
3055
     * @return void
3056
     */
3057
    public function setVersioned($bool)
3058
    {
3059
        $this->isVersioned = $bool;
3060
    }
3061
3062
    /**
3063
     * Sets the name of the field that is to be used for versioning if this class is
3064
     * versioned for optimistic locking.
3065
     *
3066
     * @param string $versionField
3067
     *
3068
     * @return void
3069
     */
3070
    public function setVersionField($versionField)
3071
    {
3072
        $this->versionField = $versionField;
3073
    }
3074
3075
    /**
3076
     * Marks this class as read only, no change tracking is applied to it.
3077
     *
3078
     * @return void
3079
     */
3080
    public function markReadOnly()
3081
    {
3082
        $this->isReadOnly = true;
3083
    }
3084
3085
    /**
3086
     * {@inheritDoc}
3087
     */
3088
    public function getFieldNames()
3089
    {
3090
        return array_keys($this->fieldMappings);
3091
    }
3092
3093
    /**
3094
     * {@inheritDoc}
3095
     */
3096
    public function getAssociationNames()
3097
    {
3098
        return array_keys($this->associationMappings);
3099
    }
3100
3101
    /**
3102
     * {@inheritDoc}
3103
     *
3104
     * @throws InvalidArgumentException
3105
     */
3106
    public function getAssociationTargetClass($assocName)
3107
    {
3108
        if ( ! isset($this->associationMappings[$assocName])) {
3109
            throw new InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
3110
        }
3111
3112
        return $this->associationMappings[$assocName]['targetEntity'];
3113
    }
3114
3115
    /**
3116
     * {@inheritDoc}
3117
     */
3118
    public function getName()
3119
    {
3120
        return $this->name;
3121
    }
3122
3123
    /**
3124
     * Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
3125
     *
3126
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3127
     *
3128
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3129
     *
3130
     * @return array
3131
     */
3132
    public function getQuotedIdentifierColumnNames($platform)
3133
    {
3134
        $quotedColumnNames = [];
3135
3136
        foreach ($this->identifier as $idProperty) {
3137
            if (isset($this->fieldMappings[$idProperty])) {
3138
                $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
3139
                    ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
3140
                    : $this->fieldMappings[$idProperty]['columnName'];
3141
3142
                continue;
3143
            }
3144
3145
            // Association defined as Id field
3146
            $joinColumns            = $this->associationMappings[$idProperty]['joinColumns'];
3147
            $assocQuotedColumnNames = array_map(
3148
                function ($joinColumn) use ($platform) {
3149
                    return isset($joinColumn['quoted'])
3150
                        ? $platform->quoteIdentifier($joinColumn['name'])
3151
                        : $joinColumn['name'];
3152
                },
3153
                $joinColumns
3154
            );
3155
3156
            $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
3157
        }
3158
3159
        return $quotedColumnNames;
3160
    }
3161
3162
    /**
3163
     * Gets the (possibly quoted) column name of a mapped field for safe use  in an SQL statement.
3164
     *
3165
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3166
     *
3167
     * @param string                                    $field
3168
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3169
     *
3170
     * @return string
3171
     */
3172
    public function getQuotedColumnName($field, $platform)
3173
    {
3174
        return isset($this->fieldMappings[$field]['quoted'])
3175
            ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName'])
3176
            : $this->fieldMappings[$field]['columnName'];
3177
    }
3178
3179
    /**
3180
     * Gets the (possibly quoted) primary table name of this class for safe use in an SQL statement.
3181
     *
3182
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3183
     *
3184
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3185
     *
3186
     * @return string
3187
     */
3188
    public function getQuotedTableName($platform)
3189
    {
3190
        return isset($this->table['quoted'])
3191
            ? $platform->quoteIdentifier($this->table['name'])
3192
            : $this->table['name'];
3193
    }
3194
3195
    /**
3196
     * Gets the (possibly quoted) name of the join table.
3197
     *
3198
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3199
     *
3200
     * @param array                                     $assoc
3201
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3202
     *
3203
     * @return string
3204
     */
3205
    public function getQuotedJoinTableName(array $assoc, $platform)
3206
    {
3207
        return isset($assoc['joinTable']['quoted'])
3208
            ? $platform->quoteIdentifier($assoc['joinTable']['name'])
3209
            : $assoc['joinTable']['name'];
3210
    }
3211
3212
    /**
3213
     * {@inheritDoc}
3214
     */
3215
    public function isAssociationInverseSide($fieldName)
3216
    {
3217
        return isset($this->associationMappings[$fieldName])
3218
            && ! $this->associationMappings[$fieldName]['isOwningSide'];
3219
    }
3220
3221
    /**
3222
     * {@inheritDoc}
3223
     */
3224
    public function getAssociationMappedByTargetField($fieldName)
3225
    {
3226
        return $this->associationMappings[$fieldName]['mappedBy'];
3227
    }
3228
3229
    /**
3230
     * @param string $targetClass
3231
     *
3232
     * @return array
3233
     */
3234
    public function getAssociationsByTargetClass($targetClass)
3235
    {
3236
        $relations = [];
3237
3238
        foreach ($this->associationMappings as $mapping) {
3239
            if ($mapping['targetEntity'] == $targetClass) {
3240
                $relations[$mapping['fieldName']] = $mapping;
3241
            }
3242
        }
3243
3244
        return $relations;
3245
    }
3246
3247
    /**
3248
     * @param  string|null $className
3249
     *
3250
     * @return string|null null if the input value is null
3251
     */
3252
    public function fullyQualifiedClassName($className)
3253
    {
3254
        if (empty($className)) {
3255
            return $className;
3256
        }
3257
3258
        if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
3259
            return $this->namespace . '\\' . $className;
3260
        }
3261
3262
        return $className;
3263
    }
3264
3265
    /**
3266
     * @param string $name
3267
     *
3268
     * @return mixed
3269
     */
3270
    public function getMetadataValue($name)
3271
    {
3272
3273
        if (isset($this->$name)) {
3274
            return $this->$name;
3275
        }
3276
3277
        return null;
3278
    }
3279
3280
    /**
3281
     * Map Embedded Class
3282
     *
3283
     * @param array $mapping
3284
     *
3285
     * @throws MappingException
3286
     * @return void
3287
     */
3288
    public function mapEmbedded(array $mapping)
3289
    {
3290
        $this->assertFieldNotMapped($mapping['fieldName']);
3291
3292
        $this->embeddedClasses[$mapping['fieldName']] = [
3293
            'class' => $this->fullyQualifiedClassName($mapping['class']),
3294
            'columnPrefix' => $mapping['columnPrefix'],
3295
            'declaredField' => $mapping['declaredField'] ?? null,
3296
            'originalField' => $mapping['originalField'] ?? null,
3297
        ];
3298
    }
3299
3300
    /**
3301
     * Inline the embeddable class
3302
     *
3303
     * @param string            $property
3304
     * @param ClassMetadataInfo $embeddable
3305
     */
3306
    public function inlineEmbeddable($property, ClassMetadataInfo $embeddable)
3307
    {
3308
        foreach ($embeddable->fieldMappings as $fieldMapping) {
3309
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->name;
3310
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
3311
                ? $property . '.' . $fieldMapping['declaredField']
3312
                : $property;
3313
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldMapping['fieldName'];
3314
            $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName'];
3315
3316
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
3317
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
3318
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
3319
                $fieldMapping['columnName'] = $this->namingStrategy
3320
                    ->embeddedFieldToColumnName(
3321
                        $property,
3322
                        $fieldMapping['columnName'],
3323
                        $this->reflClass->name,
3324
                        $embeddable->reflClass->name
3325
                    );
3326
            }
3327
3328
            $this->mapField($fieldMapping);
3329
        }
3330
    }
3331
3332
    /**
3333
     * @param string $fieldName
3334
     * @throws MappingException
3335
     */
3336
    private function assertFieldNotMapped($fieldName)
3337
    {
3338
        if (isset($this->fieldMappings[$fieldName]) ||
3339
            isset($this->associationMappings[$fieldName]) ||
3340
            isset($this->embeddedClasses[$fieldName])) {
3341
3342
            throw MappingException::duplicateFieldMapping($this->name, $fieldName);
0 ignored issues
show
Bug introduced by
The method duplicateFieldMapping() does not exist on Doctrine\ORM\Mapping\MappingException. Did you maybe mean duplicateResultSetMapping()? ( Ignorable by Annotation )

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

3342
            throw MappingException::/** @scrutinizer ignore-call */ duplicateFieldMapping($this->name, $fieldName);

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...
3343
        }
3344
    }
3345
3346
    /**
3347
     * Gets the sequence name based on class metadata.
3348
     *
3349
     * @param AbstractPlatform $platform
3350
     * @return string
3351
     *
3352
     * @todo Sequence names should be computed in DBAL depending on the platform
3353
     */
3354
    public function getSequenceName(AbstractPlatform $platform)
3355
    {
3356
        $sequencePrefix = $this->getSequencePrefix($platform);
3357
        $columnName     = $this->getSingleIdentifierColumnName();
3358
        $sequenceName   = $sequencePrefix . '_' . $columnName . '_seq';
3359
3360
        return $sequenceName;
3361
    }
3362
3363
    /**
3364
     * Gets the sequence name prefix based on class metadata.
3365
     *
3366
     * @param AbstractPlatform $platform
3367
     * @return string
3368
     *
3369
     * @todo Sequence names should be computed in DBAL depending on the platform
3370
     */
3371
    public function getSequencePrefix(AbstractPlatform $platform)
3372
    {
3373
        $tableName      = $this->getTableName();
3374
        $sequencePrefix = $tableName;
3375
3376
        // Prepend the schema name to the table name if there is one
3377
        if ($schemaName = $this->getSchemaName()) {
3378
            $sequencePrefix = $schemaName . '.' . $tableName;
3379
3380
            if ( ! $platform->supportsSchemas() && $platform->canEmulateSchemas()) {
3381
                $sequencePrefix = $schemaName . '__' . $tableName;
3382
            }
3383
        }
3384
3385
        return $sequencePrefix;
3386
    }
3387
3388
    /**
3389
     * @param array $mapping
3390
     */
3391
    private function assertMappingOrderBy(array $mapping)
3392
    {
3393
        if (isset($mapping['orderBy']) && !is_array($mapping['orderBy'])) {
3394
            throw new InvalidArgumentException("'orderBy' is expected to be an array, not " . gettype($mapping['orderBy']));
3395
        }
3396
    }
3397
}
3398