Completed
Pull Request — master (#6284)
by Luís
10:26
created

ClassMetadataInfo::getNamedNativeQueries()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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.

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

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
945 15
                        $this->embeddedClasses[$embeddedClass['declaredField']]['class'],
946 15
                        $embeddedClass['originalField']
947
                    ),
948 15
                    $this->embeddedClasses[$embeddedClass['declaredField']]['class']
949
                );
950
951 15
                continue;
952
            }
953
954 21
            $fieldRefl = $reflService->getAccessibleProperty(
955 21
                isset($embeddedClass['declared']) ? $embeddedClass['declared'] : $this->name,
956
                $property
957
            );
958
959 21
            $parentReflFields[$property] = $fieldRefl;
960 21
            $this->reflFields[$property] = $fieldRefl;
961
        }
962
963 2041
        foreach ($this->fieldMappings as $field => $mapping) {
964 2036
            if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
965 20
                $this->reflFields[$field] = new ReflectionEmbeddedProperty(
966 20
                    $parentReflFields[$mapping['declaredField']],
967 20
                    $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
0 ignored issues
show
Bug introduced by
It seems like $reflService->getAccessi...pping['originalField']) can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
968 20
                    $mapping['originalClass']
969
                );
970 20
                continue;
971
            }
972
973 2036
            $this->reflFields[$field] = isset($mapping['declared'])
974 504
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
975 2036
                : $reflService->getAccessibleProperty($this->name, $field);
976
        }
977
978 2041
        foreach ($this->associationMappings as $field => $mapping) {
979 1706
            $this->reflFields[$field] = isset($mapping['declared'])
980 406
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
981 1706
                : $reflService->getAccessibleProperty($this->name, $field);
982
        }
983 2041
    }
984
985
    /**
986
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
987
     * metadata of the class with the given name.
988
     *
989
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService The reflection service.
990
     *
991
     * @return void
992
     */
993 637
    public function initializeReflection($reflService)
994
    {
995 637
        $this->reflClass = $reflService->getClass($this->name);
996 637
        $this->namespace = $reflService->getClassNamespace($this->name);
997
998 637
        if ($this->reflClass) {
999 630
            $this->name = $this->rootEntityName = $this->reflClass->getName();
0 ignored issues
show
Bug introduced by
Consider using $this->reflClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
1000
        }
1001
1002 637
        $this->table['name'] = $this->namingStrategy->classToTableName($this->name);
1003 637
    }
1004
1005
    /**
1006
     * Validates Identifier.
1007
     *
1008
     * @return void
1009
     *
1010
     * @throws MappingException
1011
     */
1012 426
    public function validateIdentifier()
1013
    {
1014 426
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
1015 60
            return;
1016
        }
1017
1018
        // Verify & complete identifier mapping
1019 424
        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...
1020 6
            throw MappingException::identifierRequired($this->name);
1021
        }
1022
1023 418
        if ($this->usesIdGenerator() && $this->isIdentifierComposite) {
1024
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name);
1025
        }
1026 418
    }
1027
1028
    /**
1029
     * Validates association targets actually exist.
1030
     *
1031
     * @return void
1032
     *
1033
     * @throws MappingException
1034
     */
1035 427
    public function validateAssociations()
1036
    {
1037 427
        foreach ($this->associationMappings as $mapping) {
1038 275
            if ( ! ClassLoader::classExists($mapping['targetEntity']) ) {
1039 275
                throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']);
1040
            }
1041
        }
1042 426
    }
1043
1044
    /**
1045
     * Validates lifecycle callbacks.
1046
     *
1047
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService
1048
     *
1049
     * @return void
1050
     *
1051
     * @throws MappingException
1052
     */
1053 427
    public function validateLifecycleCallbacks($reflService)
1054
    {
1055 427
        foreach ($this->lifecycleCallbacks as $callbacks) {
1056 13
            foreach ($callbacks as $callbackFuncName) {
1057 13
                if ( ! $reflService->hasPublicMethod($this->name, $callbackFuncName)) {
1058 13
                    throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName);
1059
                }
1060
            }
1061
        }
1062 426
    }
1063
1064
    /**
1065
     * {@inheritDoc}
1066
     */
1067 557
    public function getReflectionClass()
1068
    {
1069 557
        return $this->reflClass;
1070
    }
1071
1072
    /**
1073
     * @param array $cache
1074
     *
1075
     * @return void
1076
     */
1077 24
    public function enableCache(array $cache)
1078
    {
1079 24
        if ( ! isset($cache['usage'])) {
1080
            $cache['usage'] = self::CACHE_USAGE_READ_ONLY;
1081
        }
1082
1083 24
        if ( ! isset($cache['region'])) {
1084 24
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName));
1085
        }
1086
1087 24
        $this->cache = $cache;
1088 24
    }
1089
1090
    /**
1091
     * @param string $fieldName
1092
     * @param array  $cache
1093
     *
1094
     * @return void
1095
     */
1096 2
    public function enableAssociationCache($fieldName, array $cache)
1097
    {
1098 2
        $this->associationMappings[$fieldName]['cache'] = $this->getAssociationCacheDefaults($fieldName, $cache);
1099 2
    }
1100
1101
    /**
1102
     * @param string $fieldName
1103
     * @param array  $cache
1104
     *
1105
     * @return array
1106
     */
1107 19
    public function getAssociationCacheDefaults($fieldName, array $cache)
1108
    {
1109 19
        if ( ! isset($cache['usage'])) {
1110 1
            $cache['usage'] = isset($this->cache['usage'])
1111 1
                ? $this->cache['usage']
1112
                : self::CACHE_USAGE_READ_ONLY;
1113
        }
1114
1115 19
        if ( ! isset($cache['region'])) {
1116 19
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
1117
        }
1118
1119 19
        return $cache;
1120
    }
1121
1122
    /**
1123
     * Sets the change tracking policy used by this class.
1124
     *
1125
     * @param integer $policy
1126
     *
1127
     * @return void
1128
     */
1129 144
    public function setChangeTrackingPolicy($policy)
1130
    {
1131 144
        $this->changeTrackingPolicy = $policy;
1132 144
    }
1133
1134
    /**
1135
     * Whether the change tracking policy of this class is "deferred explicit".
1136
     *
1137
     * @return boolean
1138
     */
1139 270
    public function isChangeTrackingDeferredExplicit()
1140
    {
1141 270
        return self::CHANGETRACKING_DEFERRED_EXPLICIT === $this->changeTrackingPolicy;
1142
    }
1143
1144
    /**
1145
     * Whether the change tracking policy of this class is "deferred implicit".
1146
     *
1147
     * @return boolean
1148
     */
1149 465
    public function isChangeTrackingDeferredImplicit()
1150
    {
1151 465
        return self::CHANGETRACKING_DEFERRED_IMPLICIT === $this->changeTrackingPolicy;
1152
    }
1153
1154
    /**
1155
     * Whether the change tracking policy of this class is "notify".
1156
     *
1157
     * @return boolean
1158
     */
1159 300
    public function isChangeTrackingNotify()
1160
    {
1161 300
        return self::CHANGETRACKING_NOTIFY === $this->changeTrackingPolicy;
1162
    }
1163
1164
    /**
1165
     * Checks whether a field is part of the identifier/primary key field(s).
1166
     *
1167
     * @param string $fieldName The field name.
1168
     *
1169
     * @return boolean TRUE if the field is part of the table identifier/primary key field(s),
1170
     *                 FALSE otherwise.
1171
     */
1172 1087
    public function isIdentifier($fieldName)
1173
    {
1174 1087
        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...
1175 1
            return false;
1176
        }
1177
1178 1086
        if ( ! $this->isIdentifierComposite) {
1179 1081
            return $fieldName === $this->identifier[0];
1180
        }
1181
1182 95
        return in_array($fieldName, $this->identifier, true);
1183
    }
1184
1185
    /**
1186
     * Checks if the field is unique.
1187
     *
1188
     * @param string $fieldName The field name.
1189
     *
1190
     * @return boolean TRUE if the field is unique, FALSE otherwise.
1191
     */
1192
    public function isUniqueField($fieldName)
1193
    {
1194
        $mapping = $this->getFieldMapping($fieldName);
1195
1196
        return false !== $mapping && isset($mapping['unique']) && $mapping['unique'];
1197
    }
1198
1199
    /**
1200
     * Checks if the field is not null.
1201
     *
1202
     * @param string $fieldName The field name.
1203
     *
1204
     * @return boolean TRUE if the field is not null, FALSE otherwise.
1205
     */
1206 1
    public function isNullable($fieldName)
1207
    {
1208 1
        $mapping = $this->getFieldMapping($fieldName);
1209
1210 1
        return false !== $mapping && isset($mapping['nullable']) && $mapping['nullable'];
1211
    }
1212
1213
    /**
1214
     * Gets a column name for a field name.
1215
     * If the column name for the field cannot be found, the given field name
1216
     * is returned.
1217
     *
1218
     * @param string $fieldName The field name.
1219
     *
1220
     * @return string The column name.
1221
     */
1222 16
    public function getColumnName($fieldName)
1223
    {
1224 16
        return isset($this->columnNames[$fieldName])
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\Cla...adataInfo::$columnNames has been deprecated with message: 3.0 Remove this.

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...
1225 16
            ? $this->columnNames[$fieldName]
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\Cla...adataInfo::$columnNames has been deprecated with message: 3.0 Remove this.

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

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...
1416
1417 553
        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...
1418 2
            throw MappingException::duplicateColumnName($this->name, $mapping['columnName']);
1419
        }
1420
1421 552
        $this->fieldNames[$mapping['columnName']] = $mapping['fieldName'];
1422
1423
        // Complete id mapping
1424 552
        if (isset($mapping['id']) && true === $mapping['id']) {
1425 514
            if ($this->versionField == $mapping['fieldName']) {
1426
                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
1427
            }
1428
1429 514
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1430 514
                $this->identifier[] = $mapping['fieldName'];
1431
            }
1432
1433
            // Check for composite key
1434 514
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1435 22
                $this->isIdentifierComposite = true;
1436
            }
1437
        }
1438
1439 552
        if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
1440 5
            if (isset($mapping['id']) && true === $mapping['id']) {
1441
                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
1442
            }
1443
1444 5
            $mapping['requireSQLConversion'] = true;
1445
        }
1446 552
    }
1447
1448
    /**
1449
     * Validates & completes the basic mapping information that is common to all
1450
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
1451
     *
1452
     * @param array $mapping The mapping.
1453
     *
1454
     * @return array The updated mapping.
1455
     *
1456
     * @throws MappingException If something is wrong with the mapping.
1457
     */
1458 358
    protected function _validateAndCompleteAssociationMapping(array $mapping)
1459
    {
1460 358
        if ( ! isset($mapping['mappedBy'])) {
1461 344
            $mapping['mappedBy'] = null;
1462
        }
1463
1464 358
        if ( ! isset($mapping['inversedBy'])) {
1465 331
            $mapping['inversedBy'] = null;
1466
        }
1467
1468 358
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
1469
1470 358
        if (empty($mapping['indexBy'])) {
1471 355
            unset($mapping['indexBy']);
1472
        }
1473
1474
        // If targetEntity is unqualified, assume it is in the same namespace as
1475
        // the sourceEntity.
1476 358
        $mapping['sourceEntity'] = $this->name;
1477
1478 358
        if (isset($mapping['targetEntity'])) {
1479 358
            $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
1480 358
            $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
1481
        }
1482
1483 358
        if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1484 1
            throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
1485
        }
1486
1487
        // Complete id mapping
1488 357
        if (isset($mapping['id']) && true === $mapping['id']) {
1489 55
            if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1490 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
1491
            }
1492
1493 54
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1494 54
                if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) {
1495
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
1496
                        $mapping['targetEntity'], $this->name, $mapping['fieldName']
1497
                    );
1498
                }
1499
1500 54
                $this->identifier[] = $mapping['fieldName'];
1501 54
                $this->containsForeignIdentifier = true;
1502
            }
1503
1504
            // Check for composite key
1505 54
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1506 26
                $this->isIdentifierComposite = true;
1507
            }
1508
1509 54
            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...
1510 3
                throw CacheException::nonCacheableEntityAssociation($this->name, $mapping['fieldName']);
1511
            }
1512
        }
1513
1514
        // Mandatory attributes for both sides
1515
        // Mandatory: fieldName, targetEntity
1516 353
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
1517
            throw MappingException::missingFieldName($this->name);
1518
        }
1519
1520 353
        if ( ! isset($mapping['targetEntity'])) {
1521
            throw MappingException::missingTargetEntity($mapping['fieldName']);
1522
        }
1523
1524
        // Mandatory and optional attributes for either side
1525 353
        if ( ! $mapping['mappedBy']) {
1526 339
            if (isset($mapping['joinTable']) && $mapping['joinTable']) {
1527 124
                if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
1528 4
                    $mapping['joinTable']['name']   = trim($mapping['joinTable']['name'], '`');
1529 339
                    $mapping['joinTable']['quoted'] = true;
1530
                }
1531
            }
1532
        } else {
1533 188
            $mapping['isOwningSide'] = false;
1534
        }
1535
1536 353
        if (isset($mapping['id']) && true === $mapping['id'] && $mapping['type'] & self::TO_MANY) {
1537 3
            throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']);
1538
        }
1539
1540
        // Fetch mode. Default fetch mode to LAZY, if not set.
1541 350
        if ( ! isset($mapping['fetch'])) {
1542 95
            $mapping['fetch'] = self::FETCH_LAZY;
1543
        }
1544
1545
        // Cascades
1546 350
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
1547
1548 350
        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1549 350
        if (in_array('all', $cascades)) {
1550 37
            $cascades = $allCascades;
1551 343
        } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
1552 1
            throw MappingException::invalidCascadeOption(
1553
                array_diff($cascades, $allCascades),
1554 1
                $this->name,
1555 1
                $mapping['fieldName']
1556
            );
1557
        }
1558
1559 349
        $mapping['cascade'] = $cascades;
1560 349
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1561 349
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1562 349
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1563 349
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1564 349
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1565
1566 349
        return $mapping;
1567
    }
1568
1569
    /**
1570
     * Validates & completes a one-to-one association mapping.
1571
     *
1572
     * @param array $mapping The mapping to validate & complete.
1573
     *
1574
     * @return array The validated & completed mapping.
1575
     *
1576
     * @throws RuntimeException
1577
     * @throws MappingException
1578
     */
1579 305
    protected function _validateAndCompleteOneToOneMapping(array $mapping)
1580
    {
1581 305
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1582
1583 299
        if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
1584 217
            $mapping['isOwningSide'] = true;
1585
        }
1586
1587 299
        if ($mapping['isOwningSide']) {
1588 286
            if (empty($mapping['joinColumns'])) {
1589
                // Apply default join column
1590 91
                $mapping['joinColumns'] = [
1591
                    [
1592 91
                        'name' => $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name),
0 ignored issues
show
Unused Code introduced by
The call to NamingStrategy::joinColumnName() has too many arguments starting with $this->name.

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

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

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1593 91
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName()
1594
                    ]
1595
                ];
1596
            }
1597
1598 286
            $uniqueConstraintColumns = [];
1599
1600 286
            foreach ($mapping['joinColumns'] as &$joinColumn) {
1601 286
                if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
1602 155
                    if (count($mapping['joinColumns']) === 1) {
1603 153
                        if (empty($mapping['id'])) {
1604 153
                            $joinColumn['unique'] = true;
1605
                        }
1606
                    } else {
1607 2
                        $uniqueConstraintColumns[] = $joinColumn['name'];
1608
                    }
1609
                }
1610
1611 286
                if (empty($joinColumn['name'])) {
1612 31
                    $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name);
0 ignored issues
show
Unused Code introduced by
The call to NamingStrategy::joinColumnName() has too many arguments starting with $this->name.

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

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

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1613
                }
1614
1615 286
                if (empty($joinColumn['referencedColumnName'])) {
1616 5
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1617
                }
1618
1619 286
                if ($joinColumn['name'][0] === '`') {
1620 7
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1621 7
                    $joinColumn['quoted'] = true;
1622
                }
1623
1624 286
                if ($joinColumn['referencedColumnName'][0] === '`') {
1625 4
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1626 4
                    $joinColumn['quoted']               = true;
1627
                }
1628
1629 286
                $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1630 286
                $mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName'])
1631
                    ? $joinColumn['fieldName']
1632 286
                    : $joinColumn['name'];
1633
            }
1634
1635 286
            if ($uniqueConstraintColumns) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uniqueConstraintColumns 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...
1636 2
                if ( ! $this->table) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->table 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...
1637
                    throw new RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.");
1638
                }
1639
1640 2
                $this->table['uniqueConstraints'][$mapping['fieldName'] . "_uniq"] = [
1641 2
                    'columns' => $uniqueConstraintColumns
1642
                ];
1643
            }
1644
1645 286
            $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
1646
        }
1647
1648 299
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1649 299
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1650
1651 299
        if ($mapping['orphanRemoval']) {
1652 22
            unset($mapping['unique']);
1653
        }
1654
1655 299
        if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
1656 2
            throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
1657
        }
1658
1659 297
        return $mapping;
1660
    }
1661
1662
    /**
1663
     * Validates & completes a one-to-many association mapping.
1664
     *
1665
     * @param array $mapping The mapping to validate and complete.
1666
     *
1667
     * @return array The validated and completed mapping.
1668
     *
1669
     * @throws MappingException
1670
     * @throws InvalidArgumentException
1671
     */
1672 133
    protected function _validateAndCompleteOneToManyMapping(array $mapping)
1673
    {
1674 133
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1675
1676
        // OneToMany-side MUST be inverse (must have mappedBy)
1677 132
        if ( ! isset($mapping['mappedBy'])) {
1678
            throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
1679
        }
1680
1681 132
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1682 132
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1683
1684 132
        $this->assertMappingOrderBy($mapping);
1685
1686 132
        return $mapping;
1687
    }
1688
1689
    /**
1690
     * Validates & completes a many-to-many association mapping.
1691
     *
1692
     * @param array $mapping The mapping to validate & complete.
1693
     *
1694
     * @return array The validated & completed mapping.
1695
     *
1696
     * @throws \InvalidArgumentException
1697
     */
1698 154
    protected function _validateAndCompleteManyToManyMapping(array $mapping)
1699
    {
1700 154
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1701
1702 152
        if ($mapping['isOwningSide']) {
1703
            // owning side MUST have a join table
1704 134
            if ( ! isset($mapping['joinTable']['name'])) {
1705 22
                $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
1706
            }
1707
1708 134
            $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
1709 134
                && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
1710
1711 134
            if ( ! isset($mapping['joinTable']['joinColumns'])) {
1712 21
                $mapping['joinTable']['joinColumns'] = [
1713
                    [
1714 21
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
1715 21
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1716 21
                        'onDelete' => 'CASCADE'
1717
                    ]
1718
                ];
1719
            }
1720
1721 134
            if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
1722 22
                $mapping['joinTable']['inverseJoinColumns'] = [
1723
                    [
1724 22
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
1725 22
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1726 22
                        'onDelete' => 'CASCADE'
1727
                    ]
1728
                ];
1729
            }
1730
1731 134
            $mapping['joinTableColumns'] = [];
1732
1733 134
            foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
1734 134
                if (empty($joinColumn['name'])) {
1735 2
                    $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
0 ignored issues
show
Documentation introduced by
$mapping['sourceEntity'] is of type array|boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1736
                }
1737
1738 134
                if (empty($joinColumn['referencedColumnName'])) {
1739 6
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1740
                }
1741
1742 134
                if ($joinColumn['name'][0] === '`') {
1743 3
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1744 3
                    $joinColumn['quoted'] = true;
1745
                }
1746
1747 134
                if ($joinColumn['referencedColumnName'][0] === '`') {
1748 3
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1749 3
                    $joinColumn['quoted']               = true;
1750
                }
1751
1752 134
                if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') {
1753 31
                    $mapping['isOnDeleteCascade'] = true;
1754
                }
1755
1756 134
                $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1757 134
                $mapping['joinTableColumns'][] = $joinColumn['name'];
1758
            }
1759
1760 134
            foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) {
1761 134
                if (empty($inverseJoinColumn['name'])) {
1762 2
                    $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']);
0 ignored issues
show
Documentation introduced by
$mapping['targetEntity'] is of type array|boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1763
                }
1764
1765 134
                if (empty($inverseJoinColumn['referencedColumnName'])) {
1766 6
                    $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1767
                }
1768
1769 134
                if ($inverseJoinColumn['name'][0] === '`') {
1770 3
                    $inverseJoinColumn['name']   = trim($inverseJoinColumn['name'], '`');
1771 3
                    $inverseJoinColumn['quoted'] = true;
1772
                }
1773
1774 134
                if ($inverseJoinColumn['referencedColumnName'][0] === '`') {
1775 3
                    $inverseJoinColumn['referencedColumnName']  = trim($inverseJoinColumn['referencedColumnName'], '`');
1776 3
                    $inverseJoinColumn['quoted']                = true;
1777
                }
1778
1779 134
                if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
1780 27
                    $mapping['isOnDeleteCascade'] = true;
1781
                }
1782
1783 134
                $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
1784 134
                $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
1785
            }
1786
        }
1787
1788 152
        $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1789
1790 152
        $this->assertMappingOrderBy($mapping);
1791
1792 152
        return $mapping;
1793
    }
1794
1795
    /**
1796
     * {@inheritDoc}
1797
     */
1798 615
    public function getIdentifierFieldNames()
1799
    {
1800 615
        return $this->identifier;
1801
    }
1802
1803
    /**
1804
     * Gets the name of the single id field. Note that this only works on
1805
     * entity classes that have a single-field pk.
1806
     *
1807
     * @return string
1808
     *
1809
     * @throws MappingException If the class has a composite primary key.
1810
     */
1811 1144
    public function getSingleIdentifierFieldName()
1812
    {
1813 1144
        if ($this->isIdentifierComposite) {
1814 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
1815
        }
1816
1817 1143
        return $this->identifier[0];
1818
    }
1819
1820
    /**
1821
     * Gets the column name of the single id column. Note that this only works on
1822
     * entity classes that have a single-field pk.
1823
     *
1824
     * @return string
1825
     *
1826
     * @throws MappingException If the class has a composite primary key.
1827
     */
1828 3
    public function getSingleIdentifierColumnName()
1829
    {
1830 3
        return $this->getColumnName($this->getSingleIdentifierFieldName());
1831
    }
1832
1833
    /**
1834
     * INTERNAL:
1835
     * Sets the mapped identifier/primary key fields of this class.
1836
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
1837
     *
1838
     * @param array $identifier
1839
     *
1840
     * @return void
1841
     */
1842 129
    public function setIdentifier(array $identifier)
1843
    {
1844 129
        $this->identifier = $identifier;
1845 129
        $this->isIdentifierComposite = (count($this->identifier) > 1);
1846 129
    }
1847
1848
    /**
1849
     * {@inheritDoc}
1850
     */
1851 63
    public function getIdentifier()
1852
    {
1853 63
        return $this->identifier;
1854
    }
1855
1856
    /**
1857
     * {@inheritDoc}
1858
     */
1859 298
    public function hasField($fieldName)
1860
    {
1861 298
        return isset($this->fieldMappings[$fieldName]);
1862
    }
1863
1864
    /**
1865
     * Gets an array containing all the column names.
1866
     *
1867
     * @param array|null $fieldNames
1868
     *
1869
     * @return array
1870
     */
1871 43
    public function getColumnNames(array $fieldNames = null)
1872
    {
1873 43
        if (null === $fieldNames) {
1874 42
            return array_keys($this->fieldNames);
1875
        }
1876
1877 1
        return array_values(array_map([$this, 'getColumnName'], $fieldNames));
1878
    }
1879
1880
    /**
1881
     * Returns an array with all the identifier column names.
1882
     *
1883
     * @return array
1884
     */
1885 325
    public function getIdentifierColumnNames()
1886
    {
1887 325
        $columnNames = [];
1888
1889 325
        foreach ($this->identifier as $idProperty) {
1890 325
            if (isset($this->fieldMappings[$idProperty])) {
1891 321
                $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
1892
1893 321
                continue;
1894
            }
1895
1896
            // Association defined as Id field
1897 22
            $joinColumns      = $this->associationMappings[$idProperty]['joinColumns'];
1898
            $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
1899
1900 22
            $columnNames = array_merge($columnNames, $assocColumnNames);
1901
        }
1902
1903 325
        return $columnNames;
1904
    }
1905
1906
    /**
1907
     * Sets the type of Id generator to use for the mapped class.
1908
     *
1909
     * @param int $generatorType
1910
     *
1911
     * @return void
1912
     */
1913 469
    public function setIdGeneratorType($generatorType)
1914
    {
1915 469
        $this->generatorType = $generatorType;
1916 469
    }
1917
1918
    /**
1919
     * Checks whether the mapped class uses an Id generator.
1920
     *
1921
     * @return boolean TRUE if the mapped class uses an Id generator, FALSE otherwise.
1922
     */
1923 418
    public function usesIdGenerator()
1924
    {
1925 418
        return $this->generatorType != self::GENERATOR_TYPE_NONE;
1926
    }
1927
1928
    /**
1929
     * @return boolean
1930
     */
1931 1357
    public function isInheritanceTypeNone()
1932
    {
1933 1357
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1934
    }
1935
1936
    /**
1937
     * Checks whether the mapped class uses the JOINED inheritance mapping strategy.
1938
     *
1939
     * @return boolean TRUE if the class participates in a JOINED inheritance mapping,
1940
     *                 FALSE otherwise.
1941
     */
1942 1062
    public function isInheritanceTypeJoined()
1943
    {
1944 1062
        return $this->inheritanceType == self::INHERITANCE_TYPE_JOINED;
1945
    }
1946
1947
    /**
1948
     * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy.
1949
     *
1950
     * @return boolean TRUE if the class participates in a SINGLE_TABLE inheritance mapping,
1951
     *                 FALSE otherwise.
1952
     */
1953 1241
    public function isInheritanceTypeSingleTable()
1954
    {
1955 1241
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE;
1956
    }
1957
1958
    /**
1959
     * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy.
1960
     *
1961
     * @return boolean TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping,
1962
     *                 FALSE otherwise.
1963
     */
1964 265
    public function isInheritanceTypeTablePerClass()
1965
    {
1966 265
        return $this->inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
1967
    }
1968
1969
    /**
1970
     * Checks whether the class uses an identity column for the Id generation.
1971
     *
1972
     * @return boolean TRUE if the class uses the IDENTITY generator, FALSE otherwise.
1973
     */
1974 1076
    public function isIdGeneratorIdentity()
1975
    {
1976 1076
        return $this->generatorType == self::GENERATOR_TYPE_IDENTITY;
1977
    }
1978
1979
    /**
1980
     * Checks whether the class uses a sequence for id generation.
1981
     *
1982
     * @return boolean TRUE if the class uses the SEQUENCE generator, FALSE otherwise.
1983
     */
1984 323
    public function isIdGeneratorSequence()
1985
    {
1986 323
        return $this->generatorType == self::GENERATOR_TYPE_SEQUENCE;
1987
    }
1988
1989
    /**
1990
     * Checks whether the class uses a table for id generation.
1991
     *
1992
     * @return boolean TRUE if the class uses the TABLE generator, FALSE otherwise.
1993
     */
1994 85
    public function isIdGeneratorTable()
1995
    {
1996 85
        return $this->generatorType == self::GENERATOR_TYPE_TABLE;
1997
    }
1998
1999
    /**
2000
     * Checks whether the class has a natural identifier/pk (which means it does
2001
     * not use any Id generator.
2002
     *
2003
     * @return boolean
2004
     */
2005 74
    public function isIdentifierNatural()
2006
    {
2007 74
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
2008
    }
2009
2010
    /**
2011
     * Checks whether the class use a UUID for id generation.
2012
     *
2013
     * @return boolean
2014
     */
2015
    public function isIdentifierUuid()
2016
    {
2017
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
2018
    }
2019
2020
    /**
2021
     * Gets the type of a field.
2022
     *
2023
     * @param string $fieldName
2024
     *
2025
     * @return \Doctrine\DBAL\Types\Type|string|null
2026
     *
2027
     * @todo 3.0 Remove this. PersisterHelper should fix it somehow
2028
     */
2029 931
    public function getTypeOfField($fieldName)
2030
    {
2031 931
        return isset($this->fieldMappings[$fieldName])
2032 931
            ? $this->fieldMappings[$fieldName]['type']
2033 931
            : null;
2034
    }
2035
2036
    /**
2037
     * Gets the type of a column.
2038
     *
2039
     * @param string $columnName
2040
     *
2041
     * @return \Doctrine\DBAL\Types\Type|string|null
2042
     *
2043
     * @deprecated 3.0 remove this. this method is bogous and unreliable, since it cannot resolve the type of a column
2044
     *             that is derived by a referenced field on a different entity.
2045
     */
2046
    public function getTypeOfColumn($columnName)
2047
    {
2048
        return $this->getTypeOfField($this->getFieldName($columnName));
2049
    }
2050
2051
    /**
2052
     * Gets the name of the primary table.
2053
     *
2054
     * @return string
2055
     */
2056 1364
    public function getTableName()
2057
    {
2058 1364
        return $this->table['name'];
2059
    }
2060
2061
    /**
2062
     * Gets primary table's schema name.
2063
     *
2064
     * @return string|null
2065
     */
2066 13
    public function getSchemaName()
2067
    {
2068 13
        return isset($this->table['schema']) ? $this->table['schema'] : null;
2069
    }
2070
2071
    /**
2072
     * Gets the table name to use for temporary identifier tables of this class.
2073
     *
2074
     * @return string
2075
     */
2076 7
    public function getTemporaryIdTableName()
2077
    {
2078
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
2079 7
        return str_replace('.', '_', $this->getTableName() . '_id_tmp');
2080
    }
2081
2082
    /**
2083
     * Sets the mapped subclasses of this class.
2084
     *
2085
     * @param array $subclasses The names of all mapped subclasses.
2086
     *
2087
     * @return void
2088
     */
2089 2
    public function setSubclasses(array $subclasses)
2090
    {
2091 2
        foreach ($subclasses as $subclass) {
2092 2
            $this->subClasses[] = $this->fullyQualifiedClassName($subclass);
2093
        }
2094 2
    }
2095
2096
    /**
2097
     * Sets the parent class names.
2098
     * Assumes that the class names in the passed array are in the order:
2099
     * directParent -> directParentParent -> directParentParentParent ... -> root.
2100
     *
2101
     * @param array $classNames
2102
     *
2103
     * @return void
2104
     */
2105 435
    public function setParentClasses(array $classNames)
2106
    {
2107 435
        $this->parentClasses = $classNames;
2108
2109 435
        if (count($classNames) > 0) {
2110 84
            $this->rootEntityName = array_pop($classNames);
2111
        }
2112 435
    }
2113
2114
    /**
2115
     * Sets the inheritance type used by the class and its subclasses.
2116
     *
2117
     * @param integer $type
2118
     *
2119
     * @return void
2120
     *
2121
     * @throws MappingException
2122
     */
2123 175
    public function setInheritanceType($type)
2124
    {
2125 175
        if ( ! $this->_isInheritanceType($type)) {
2126
            throw MappingException::invalidInheritanceType($this->name, $type);
2127
        }
2128
2129 175
        $this->inheritanceType = $type;
2130 175
    }
2131
2132
    /**
2133
     * Sets the association to override association mapping of property for an entity relationship.
2134
     *
2135
     * @param string $fieldName
2136
     * @param array  $overrideMapping
2137
     *
2138
     * @return void
2139
     *
2140
     * @throws MappingException
2141
     */
2142 20
    public function setAssociationOverride($fieldName, array $overrideMapping)
2143
    {
2144 20
        if ( ! isset($this->associationMappings[$fieldName])) {
2145 1
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2146
        }
2147
2148 19
        $mapping = $this->associationMappings[$fieldName];
2149
2150 19
        if (isset($overrideMapping['joinColumns'])) {
2151 13
            $mapping['joinColumns'] = $overrideMapping['joinColumns'];
2152
        }
2153
2154 19
        if (isset($overrideMapping['inversedBy'])) {
2155 6
            $mapping['inversedBy'] = $overrideMapping['inversedBy'];
2156
        }
2157
2158 19
        if (isset($overrideMapping['joinTable'])) {
2159 12
            $mapping['joinTable'] = $overrideMapping['joinTable'];
2160
        }
2161
2162 19
        $mapping['joinColumnFieldNames']        = null;
2163 19
        $mapping['joinTableColumns']            = null;
2164 19
        $mapping['sourceToTargetKeyColumns']    = null;
2165 19
        $mapping['relationToSourceKeyColumns']  = null;
2166 19
        $mapping['relationToTargetKeyColumns']  = null;
2167
2168 19
        switch ($mapping['type']) {
2169 19
            case self::ONE_TO_ONE:
2170 1
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2171 1
                break;
2172 18
            case self::ONE_TO_MANY:
2173
                $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2174
                break;
2175 18
            case self::MANY_TO_ONE:
2176 12
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2177 12
                break;
2178 18
            case self::MANY_TO_MANY:
2179 18
                $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2180 18
                break;
2181
        }
2182
2183 19
        $this->associationMappings[$fieldName] = $mapping;
2184 19
    }
2185
2186
    /**
2187
     * Sets the override for a mapped field.
2188
     *
2189
     * @param string $fieldName
2190
     * @param array  $overrideMapping
2191
     *
2192
     * @return void
2193
     *
2194
     * @throws MappingException
2195
     */
2196 15
    public function setAttributeOverride($fieldName, array $overrideMapping)
2197
    {
2198 15
        if ( ! isset($this->fieldMappings[$fieldName])) {
2199 1
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2200
        }
2201
2202 14
        $mapping = $this->fieldMappings[$fieldName];
2203
2204 14
        if (isset($mapping['id'])) {
2205 12
            $overrideMapping['id'] = $mapping['id'];
2206
        }
2207
2208 14
        if ( ! isset($overrideMapping['type'])) {
2209 6
            $overrideMapping['type'] = $mapping['type'];
2210
        }
2211
2212 14
        if ( ! isset($overrideMapping['fieldName'])) {
2213 5
            $overrideMapping['fieldName'] = $mapping['fieldName'];
2214
        }
2215
2216 14
        if ($overrideMapping['type'] !== $mapping['type']) {
2217 1
            throw MappingException::invalidOverrideFieldType($this->name, $fieldName);
2218
        }
2219
2220 13
        unset($this->fieldMappings[$fieldName]);
2221 13
        unset($this->fieldNames[$mapping['columnName']]);
2222 13
        unset($this->columnNames[$mapping['fieldName']]);
2223
2224 13
        $this->_validateAndCompleteFieldMapping($overrideMapping);
2225
2226 13
        $this->fieldMappings[$fieldName] = $overrideMapping;
2227 13
    }
2228
2229
    /**
2230
     * Checks whether a mapped field is inherited from an entity superclass.
2231
     *
2232
     * @param string $fieldName
2233
     *
2234
     * @return bool TRUE if the field is inherited, FALSE otherwise.
2235
     */
2236 395
    public function isInheritedField($fieldName)
2237
    {
2238 395
        return isset($this->fieldMappings[$fieldName]['inherited']);
2239
    }
2240
2241
    /**
2242
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
2243
     *
2244
     * @return bool
2245
     */
2246 434
    public function isRootEntity()
2247
    {
2248 434
        return $this->name == $this->rootEntityName;
2249
    }
2250
2251
    /**
2252
     * Checks whether a mapped association field is inherited from a superclass.
2253
     *
2254
     * @param string $fieldName
2255
     *
2256
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
2257
     */
2258 374
    public function isInheritedAssociation($fieldName)
2259
    {
2260 374
        return isset($this->associationMappings[$fieldName]['inherited']);
2261
    }
2262
2263 374
    public function isInheritedEmbeddedClass($fieldName)
2264
    {
2265 374
        return isset($this->embeddedClasses[$fieldName]['inherited']);
2266
    }
2267
2268
    /**
2269
     * Sets the name of the primary table the class is mapped to.
2270
     *
2271
     * @param string $tableName The table name.
2272
     *
2273
     * @return void
2274
     *
2275
     * @deprecated Use {@link setPrimaryTable}.
2276
     */
2277 5
    public function setTableName($tableName)
2278
    {
2279 5
        $this->table['name'] = $tableName;
2280 5
    }
2281
2282
    /**
2283
     * Sets the primary table definition. The provided array supports the
2284
     * following structure:
2285
     *
2286
     * name => <tableName> (optional, defaults to class name)
2287
     * indexes => array of indexes (optional)
2288
     * uniqueConstraints => array of constraints (optional)
2289
     *
2290
     * If a key is omitted, the current value is kept.
2291
     *
2292
     * @param array $table The table description.
2293
     *
2294
     * @return void
2295
     */
2296 335
    public function setPrimaryTable(array $table)
2297
    {
2298 335
        if (isset($table['name'])) {
2299
            // Split schema and table name from a table name like "myschema.mytable"
2300 272
            if (strpos($table['name'], '.') !== false) {
2301 9
                list($this->table['schema'], $table['name']) = explode('.', $table['name'], 2);
2302
            }
2303
2304 272
            if ($table['name'][0] === '`') {
2305 17
                $table['name']          = trim($table['name'], '`');
2306 17
                $this->table['quoted']  = true;
2307
            }
2308
2309 272
            $this->table['name'] = $table['name'];
2310
        }
2311
2312 335
        if (isset($table['schema'])) {
2313 6
            $this->table['schema'] = $table['schema'];
2314
        }
2315
2316 335
        if (isset($table['indexes'])) {
2317 17
            $this->table['indexes'] = $table['indexes'];
2318
        }
2319
2320 335
        if (isset($table['uniqueConstraints'])) {
2321 8
            $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
2322
        }
2323
2324 335
        if (isset($table['options'])) {
2325 9
            $this->table['options'] = $table['options'];
2326
        }
2327 335
    }
2328
2329
    /**
2330
     * Checks whether the given type identifies an inheritance type.
2331
     *
2332
     * @param integer $type
2333
     *
2334
     * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise.
2335
     */
2336 175
    private function _isInheritanceType($type)
2337
    {
2338 175
        return $type == self::INHERITANCE_TYPE_NONE ||
2339 110
                $type == self::INHERITANCE_TYPE_SINGLE_TABLE ||
2340 54
                $type == self::INHERITANCE_TYPE_JOINED ||
2341 175
                $type == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
2342
    }
2343
2344
    /**
2345
     * Adds a mapped field to the class.
2346
     *
2347
     * @param array $mapping The field mapping.
2348
     *
2349
     * @return void
2350
     *
2351
     * @throws MappingException
2352
     */
2353 554
    public function mapField(array $mapping)
2354
    {
2355 554
        $this->_validateAndCompleteFieldMapping($mapping);
2356 552
        $this->assertFieldNotMapped($mapping['fieldName']);
2357
2358 551
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2359 551
    }
2360
2361
    /**
2362
     * INTERNAL:
2363
     * Adds an association mapping without completing/validating it.
2364
     * This is mainly used to add inherited association mappings to derived classes.
2365
     *
2366
     * @param array $mapping
2367
     *
2368
     * @return void
2369
     *
2370
     * @throws MappingException
2371
     */
2372 49
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
2373
    {
2374 49
        if (isset($this->associationMappings[$mapping['fieldName']])) {
2375 1
            throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']);
2376
        }
2377 49
        $this->associationMappings[$mapping['fieldName']] = $mapping;
2378 49
    }
2379
2380
    /**
2381
     * INTERNAL:
2382
     * Adds a field mapping without completing/validating it.
2383
     * This is mainly used to add inherited field mappings to derived classes.
2384
     *
2385
     * @param array $fieldMapping
2386
     *
2387
     * @return void
2388
     */
2389 113
    public function addInheritedFieldMapping(array $fieldMapping)
2390
    {
2391 113
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
2392 113
        $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 with message: 3.0 Remove this.

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

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

Loading history...
2393 113
        $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
2394 113
    }
2395
2396
    /**
2397
     * INTERNAL:
2398
     * Adds a named query to this class.
2399
     *
2400
     * @param array $queryMapping
2401
     *
2402
     * @return void
2403
     *
2404
     * @throws MappingException
2405
     */
2406 30
    public function addNamedQuery(array $queryMapping)
2407
    {
2408 30
        if (!isset($queryMapping['name'])) {
2409 2
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2410
        }
2411
2412 28
        if (isset($this->namedQueries[$queryMapping['name']])) {
2413 1
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2414
        }
2415
2416 28
        if (!isset($queryMapping['query'])) {
2417
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2418
        }
2419
2420 28
        $name   = $queryMapping['name'];
2421 28
        $query  = $queryMapping['query'];
2422 28
        $dql    = str_replace('__CLASS__', $this->name, $query);
2423
2424 28
        $this->namedQueries[$name] = [
2425 28
            'name'  => $name,
2426 28
            'query' => $query,
2427 28
            'dql'   => $dql,
2428
        ];
2429 28
    }
2430
2431
    /**
2432
     * INTERNAL:
2433
     * Adds a named native query to this class.
2434
     *
2435
     * @param array $queryMapping
2436
     *
2437
     * @return void
2438
     *
2439
     * @throws MappingException
2440
     */
2441 41
    public function addNamedNativeQuery(array $queryMapping)
2442
    {
2443 41
        if (!isset($queryMapping['name'])) {
2444
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2445
        }
2446
2447 41
        if (isset($this->namedNativeQueries[$queryMapping['name']])) {
2448 1
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2449
        }
2450
2451 41
        if (!isset($queryMapping['query'])) {
2452
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2453
        }
2454
2455 41
        if (!isset($queryMapping['resultClass']) && !isset($queryMapping['resultSetMapping'])) {
2456
            throw MappingException::missingQueryMapping($this->name, $queryMapping['name']);
2457
        }
2458
2459 41
        $queryMapping['isSelfClass'] = false;
2460
2461 41
        if (isset($queryMapping['resultClass'])) {
2462 39
            if ($queryMapping['resultClass'] === '__CLASS__') {
2463
2464 12
                $queryMapping['isSelfClass'] = true;
2465 12
                $queryMapping['resultClass'] = $this->name;
2466
            }
2467
2468 39
            $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
0 ignored issues
show
Bug introduced by
It seems like $queryMapping['resultClass'] can also be of type boolean; however, Doctrine\ORM\Mapping\Cla...llyQualifiedClassName() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2469 39
            $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\');
2470
        }
2471
2472 41
        $this->namedNativeQueries[$queryMapping['name']] = $queryMapping;
2473 41
    }
2474
2475
    /**
2476
     * INTERNAL:
2477
     * Adds a sql result set mapping to this class.
2478
     *
2479
     * @param array $resultMapping
2480
     *
2481
     * @return void
2482
     *
2483
     * @throws MappingException
2484
     */
2485 41
    public function addSqlResultSetMapping(array $resultMapping)
2486
    {
2487 41
        if (!isset($resultMapping['name'])) {
2488
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name);
2489
        }
2490
2491 41
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
2492 1
            throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']);
2493
        }
2494
2495 41
        if (isset($resultMapping['entities'])) {
2496 41
            foreach ($resultMapping['entities'] as $key => $entityResult) {
2497 41
                if (!isset($entityResult['entityClass'])) {
2498 1
                    throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']);
2499
                }
2500
2501 40
                $entityResult['isSelfClass'] = false;
2502 40
                if ($entityResult['entityClass'] === '__CLASS__') {
2503
2504 22
                    $entityResult['isSelfClass'] = true;
2505 22
                    $entityResult['entityClass'] = $this->name;
2506
2507
                }
2508
2509 40
                $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']);
0 ignored issues
show
Bug introduced by
It seems like $entityResult['entityClass'] can also be of type boolean; however, Doctrine\ORM\Mapping\Cla...llyQualifiedClassName() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2510
2511 40
                $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
2512 40
                $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
2513
2514 40
                if (isset($entityResult['fields'])) {
2515 36
                    foreach ($entityResult['fields'] as $k => $field) {
0 ignored issues
show
Bug introduced by
The expression $entityResult['fields'] of type boolean|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

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