Failed Conditions
Push — 2.7 ( c036c0...266f0d )
by Jonathan
57:23 queued 50:07
created

lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php (1 issue)

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;
30
use Doctrine\ORM\Cache\CacheException;
31
32
/**
33
 * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
34
 * of an entity and its associations.
35
 *
36
 * Once populated, ClassMetadata instances are usually cached in a serialized form.
37
 *
38
 * <b>IMPORTANT NOTE:</b>
39
 *
40
 * The fields of this class are only public for 2 reasons:
41
 * 1) To allow fast READ access.
42
 * 2) To drastically reduce the size of a serialized instance (private/protected members
43
 *    get the whole class name, namespace inclusive, prepended to every property in
44
 *    the serialized representation).
45
 *
46
 * @author Roman Borschel <[email protected]>
47
 * @author Jonathan H. Wage <[email protected]>
48
 * @since 2.0
49
 */
50
class ClassMetadataInfo implements ClassMetadata
51
{
52
    /* The inheritance mapping types */
53
    /**
54
     * NONE means the class does not participate in an inheritance hierarchy
55
     * and therefore does not need an inheritance mapping type.
56
     */
57
    const INHERITANCE_TYPE_NONE = 1;
58
59
    /**
60
     * JOINED means the class will be persisted according to the rules of
61
     * <tt>Class Table Inheritance</tt>.
62
     */
63
    const INHERITANCE_TYPE_JOINED = 2;
64
65
    /**
66
     * SINGLE_TABLE means the class will be persisted according to the rules of
67
     * <tt>Single Table Inheritance</tt>.
68
     */
69
    const INHERITANCE_TYPE_SINGLE_TABLE = 3;
70
71
    /**
72
     * TABLE_PER_CLASS means the class will be persisted according to the rules
73
     * of <tt>Concrete Table Inheritance</tt>.
74
     */
75
    const INHERITANCE_TYPE_TABLE_PER_CLASS = 4;
76
77
    /* The Id generator types. */
78
    /**
79
     * AUTO means the generator type will depend on what the used platform prefers.
80
     * Offers full portability.
81
     */
82
    const GENERATOR_TYPE_AUTO = 1;
83
84
    /**
85
     * SEQUENCE means a separate sequence object will be used. Platforms that do
86
     * not have native sequence support may emulate it. Full portability is currently
87
     * not guaranteed.
88
     */
89
    const GENERATOR_TYPE_SEQUENCE = 2;
90
91
    /**
92
     * TABLE means a separate table is used for id generation.
93
     * Offers full portability.
94
     */
95
    const GENERATOR_TYPE_TABLE = 3;
96
97
    /**
98
     * IDENTITY means an identity column is used for id generation. The database
99
     * will fill in the id column on insertion. Platforms that do not support
100
     * native identity columns may emulate them. Full portability is currently
101
     * not guaranteed.
102
     */
103
    const GENERATOR_TYPE_IDENTITY = 4;
104
105
    /**
106
     * NONE means the class does not have a generated id. That means the class
107
     * must have a natural, manually assigned id.
108
     */
109
    const GENERATOR_TYPE_NONE = 5;
110
111
    /**
112
     * UUID means that a UUID/GUID expression is used for id generation. Full
113
     * portability is currently not guaranteed.
114
     */
115
    const GENERATOR_TYPE_UUID = 6;
116
117
    /**
118
     * CUSTOM means that customer will use own ID generator that supposedly work
119
     */
120
    const GENERATOR_TYPE_CUSTOM = 7;
121
122
    /**
123
     * DEFERRED_IMPLICIT means that changes of entities are calculated at commit-time
124
     * by doing a property-by-property comparison with the original data. This will
125
     * be done for all entities that are in MANAGED state at commit-time.
126
     *
127
     * This is the default change tracking policy.
128
     */
129
    const CHANGETRACKING_DEFERRED_IMPLICIT = 1;
130
131
    /**
132
     * DEFERRED_EXPLICIT means that changes of entities are calculated at commit-time
133
     * by doing a property-by-property comparison with the original data. This will
134
     * be done only for entities that were explicitly saved (through persist() or a cascade).
135
     */
136
    const CHANGETRACKING_DEFERRED_EXPLICIT = 2;
137
138
    /**
139
     * NOTIFY means that Doctrine relies on the entities sending out notifications
140
     * when their properties change. Such entity classes must implement
141
     * the <tt>NotifyPropertyChanged</tt> interface.
142
     */
143
    const CHANGETRACKING_NOTIFY = 3;
144
145
    /**
146
     * Specifies that an association is to be fetched when it is first accessed.
147
     */
148
    const FETCH_LAZY = 2;
149
150
    /**
151
     * Specifies that an association is to be fetched when the owner of the
152
     * association is fetched.
153
     */
154
    const FETCH_EAGER = 3;
155
156
    /**
157
     * Specifies that an association is to be fetched lazy (on first access) and that
158
     * commands such as Collection#count, Collection#slice are issued directly against
159
     * the database if the collection is not yet initialized.
160
     */
161
    const FETCH_EXTRA_LAZY = 4;
162
163
    /**
164
     * Identifies a one-to-one association.
165
     */
166
    const ONE_TO_ONE = 1;
167
168
    /**
169
     * Identifies a many-to-one association.
170
     */
171
    const MANY_TO_ONE = 2;
172
173
    /**
174
     * Identifies a one-to-many association.
175
     */
176
    const ONE_TO_MANY = 4;
177
178
    /**
179
     * Identifies a many-to-many association.
180
     */
181
    const MANY_TO_MANY = 8;
182
183
    /**
184
     * Combined bitmask for to-one (single-valued) associations.
185
     */
186
    const TO_ONE = 3;
187
188
    /**
189
     * Combined bitmask for to-many (collection-valued) associations.
190
     */
191
    const TO_MANY = 12;
192
193
    /**
194
     * ReadOnly cache can do reads, inserts and deletes, cannot perform updates or employ any locks,
195
     */
196
    const CACHE_USAGE_READ_ONLY = 1;
197
198
    /**
199
     * Nonstrict Read Write Cache doesn’t employ any locks but can do inserts, update and deletes.
200
     */
201
    const CACHE_USAGE_NONSTRICT_READ_WRITE = 2;
202
203
    /**
204
     * Read Write Attempts to lock the entity before update/delete.
205
     */
206
    const CACHE_USAGE_READ_WRITE = 3;
207
208
    /**
209
     * READ-ONLY: The name of the entity class.
210
     *
211
     * @var string
212
     */
213
    public $name;
214
215
    /**
216
     * READ-ONLY: The namespace the entity class is contained in.
217
     *
218
     * @var string
219
     *
220
     * @todo Not really needed. Usage could be localized.
221
     */
222
    public $namespace;
223
224
    /**
225
     * READ-ONLY: The name of the entity class that is at the root of the mapped entity inheritance
226
     * hierarchy. If the entity is not part of a mapped inheritance hierarchy this is the same
227
     * as {@link $name}.
228
     *
229
     * @var string
230
     */
231
    public $rootEntityName;
232
233
    /**
234
     * READ-ONLY: The definition of custom generator. Only used for CUSTOM
235
     * generator type
236
     *
237
     * The definition has the following structure:
238
     * <code>
239
     * array(
240
     *     'class' => 'ClassName',
241
     * )
242
     * </code>
243
     *
244
     * @var array
245
     *
246
     * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
247
     */
248
    public $customGeneratorDefinition;
249
250
    /**
251
     * The name of the custom repository class used for the entity class.
252
     * (Optional).
253
     *
254
     * @var string
255
     */
256
    public $customRepositoryClassName;
257
258
    /**
259
     * READ-ONLY: Whether this class describes the mapping of a mapped superclass.
260
     *
261
     * @var boolean
262
     */
263
    public $isMappedSuperclass = false;
264
265
    /**
266
     * READ-ONLY: Whether this class describes the mapping of an embeddable class.
267
     *
268
     * @var boolean
269
     */
270
    public $isEmbeddedClass = false;
271
272
    /**
273
     * READ-ONLY: The names of the parent classes (ancestors).
274
     *
275
     * @var array
276
     */
277
    public $parentClasses = [];
278
279
    /**
280
     * READ-ONLY: The names of all subclasses (descendants).
281
     *
282
     * @var array
283
     */
284
    public $subClasses = [];
285
286
    /**
287
     * READ-ONLY: The names of all embedded classes based on properties.
288
     *
289
     * @var array
290
     */
291
    public $embeddedClasses = [];
292
293
    /**
294
     * READ-ONLY: The named queries allowed to be called directly from Repository.
295
     *
296
     * @var array
297
     */
298
    public $namedQueries = [];
299
300
    /**
301
     * READ-ONLY: The named native queries allowed to be called directly from Repository.
302
     *
303
     * A native SQL named query definition has the following structure:
304
     * <pre>
305
     * array(
306
     *     'name'               => <query name>,
307
     *     'query'              => <sql query>,
308
     *     'resultClass'        => <class of the result>,
309
     *     'resultSetMapping'   => <name of a SqlResultSetMapping>
310
     * )
311
     * </pre>
312
     *
313
     * @var array
314
     */
315
    public $namedNativeQueries = [];
316
317
    /**
318
     * READ-ONLY: The mappings of the results of native SQL queries.
319
     *
320
     * A native result mapping definition has the following structure:
321
     * <pre>
322
     * array(
323
     *     'name'               => <result name>,
324
     *     'entities'           => array(<entity result mapping>),
325
     *     'columns'            => array(<column result mapping>)
326
     * )
327
     * </pre>
328
     *
329
     * @var array
330
     */
331
    public $sqlResultSetMappings = [];
332
333
    /**
334
     * READ-ONLY: The field names of all fields that are part of the identifier/primary key
335
     * of the mapped entity class.
336
     *
337
     * @var array
338
     */
339
    public $identifier = [];
340
341
    /**
342
     * READ-ONLY: The inheritance mapping type used by the class.
343
     *
344
     * @var integer
345
     */
346
    public $inheritanceType = self::INHERITANCE_TYPE_NONE;
347
348
    /**
349
     * READ-ONLY: The Id generator type used by the class.
350
     *
351
     * @var int
352
     */
353
    public $generatorType = self::GENERATOR_TYPE_NONE;
354
355
    /**
356
     * READ-ONLY: The field mappings of the class.
357
     * Keys are field names and values are mapping definitions.
358
     *
359
     * The mapping definition array has the following values:
360
     *
361
     * - <b>fieldName</b> (string)
362
     * The name of the field in the Entity.
363
     *
364
     * - <b>type</b> (string)
365
     * The type name of the mapped field. Can be one of Doctrine's mapping types
366
     * or a custom mapping type.
367
     *
368
     * - <b>columnName</b> (string, optional)
369
     * The column name. Optional. Defaults to the field name.
370
     *
371
     * - <b>length</b> (integer, optional)
372
     * The database length of the column. Optional. Default value taken from
373
     * the type.
374
     *
375
     * - <b>id</b> (boolean, optional)
376
     * Marks the field as the primary key of the entity. Multiple fields of an
377
     * entity can have the id attribute, forming a composite key.
378
     *
379
     * - <b>nullable</b> (boolean, optional)
380
     * Whether the column is nullable. Defaults to FALSE.
381
     *
382
     * - <b>columnDefinition</b> (string, optional, schema-only)
383
     * The SQL fragment that is used when generating the DDL for the column.
384
     *
385
     * - <b>precision</b> (integer, optional, schema-only)
386
     * The precision of a decimal column. Only valid if the column type is decimal.
387
     *
388
     * - <b>scale</b> (integer, optional, schema-only)
389
     * The scale of a decimal column. Only valid if the column type is decimal.
390
     *
391
     * - <b>'unique'</b> (string, optional, schema-only)
392
     * Whether a unique constraint should be generated for the column.
393
     *
394
     * @var array
395
     */
396
    public $fieldMappings = [];
397
398
    /**
399
     * READ-ONLY: An array of field names. Used to look up field names from column names.
400
     * Keys are column names and values are field names.
401
     *
402
     * @var array
403
     */
404
    public $fieldNames = [];
405
406
    /**
407
     * READ-ONLY: A map of field names to column names. Keys are field names and values column names.
408
     * Used to look up column names from field names.
409
     * This is the reverse lookup map of $_fieldNames.
410
     *
411
     * @var array
412
     *
413
     * @deprecated 3.0 Remove this.
414
     */
415
    public $columnNames = [];
416
417
    /**
418
     * READ-ONLY: The discriminator value of this class.
419
     *
420
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
421
     * where a discriminator column is used.</b>
422
     *
423
     * @var mixed
424
     *
425
     * @see discriminatorColumn
426
     */
427
    public $discriminatorValue;
428
429
    /**
430
     * READ-ONLY: The discriminator map of all mapped classes in the hierarchy.
431
     *
432
     * <b>This does only apply to the JOINED and SINGLE_TABLE inheritance mapping strategies
433
     * where a discriminator column is used.</b>
434
     *
435
     * @var mixed
436
     *
437
     * @see discriminatorColumn
438
     */
439
    public $discriminatorMap = [];
440
441
    /**
442
     * READ-ONLY: The definition of the discriminator column used in JOINED and SINGLE_TABLE
443
     * inheritance mappings.
444
     *
445
     * @var array
446
     */
447
    public $discriminatorColumn;
448
449
    /**
450
     * READ-ONLY: The primary table definition. The definition is an array with the
451
     * following entries:
452
     *
453
     * name => <tableName>
454
     * schema => <schemaName>
455
     * indexes => array
456
     * uniqueConstraints => array
457
     *
458
     * @var array
459
     */
460
    public $table;
461
462
    /**
463
     * READ-ONLY: The registered lifecycle callbacks for entities of this class.
464
     *
465
     * @var array[]
466
     */
467
    public $lifecycleCallbacks = [];
468
469
    /**
470
     * READ-ONLY: The registered entity listeners.
471
     *
472
     * @var array
473
     */
474
    public $entityListeners = [];
475
476
    /**
477
     * READ-ONLY: The association mappings of this class.
478
     *
479
     * The mapping definition array supports the following keys:
480
     *
481
     * - <b>fieldName</b> (string)
482
     * The name of the field in the entity the association is mapped to.
483
     *
484
     * - <b>targetEntity</b> (string)
485
     * The class name of the target entity. If it is fully-qualified it is used as is.
486
     * If it is a simple, unqualified class name the namespace is assumed to be the same
487
     * as the namespace of the source entity.
488
     *
489
     * - <b>mappedBy</b> (string, required for bidirectional associations)
490
     * The name of the field that completes the bidirectional association on the owning side.
491
     * This key must be specified on the inverse side of a bidirectional association.
492
     *
493
     * - <b>inversedBy</b> (string, required for bidirectional associations)
494
     * The name of the field that completes the bidirectional association on the inverse side.
495
     * This key must be specified on the owning side of a bidirectional association.
496
     *
497
     * - <b>cascade</b> (array, optional)
498
     * The names of persistence operations to cascade on the association. The set of possible
499
     * values are: "persist", "remove", "detach", "merge", "refresh", "all" (implies all others).
500
     *
501
     * - <b>orderBy</b> (array, one-to-many/many-to-many only)
502
     * A map of field names (of the target entity) to sorting directions (ASC/DESC).
503
     * Example: array('priority' => 'desc')
504
     *
505
     * - <b>fetch</b> (integer, optional)
506
     * The fetching strategy to use for the association, usually defaults to FETCH_LAZY.
507
     * Possible values are: ClassMetadata::FETCH_EAGER, ClassMetadata::FETCH_LAZY.
508
     *
509
     * - <b>joinTable</b> (array, optional, many-to-many only)
510
     * Specification of the join table and its join columns (foreign keys).
511
     * Only valid for many-to-many mappings. Note that one-to-many associations can be mapped
512
     * through a join table by simply mapping the association as many-to-many with a unique
513
     * constraint on the join table.
514
     *
515
     * - <b>indexBy</b> (string, optional, to-many only)
516
     * Specification of a field on target-entity that is used to index the collection by.
517
     * This field HAS to be either the primary key or a unique column. Otherwise the collection
518
     * does not contain all the entities that are actually related.
519
     *
520
     * A join table definition has the following structure:
521
     * <pre>
522
     * array(
523
     *     'name' => <join table name>,
524
     *      'joinColumns' => array(<join column mapping from join table to source table>),
525
     *      'inverseJoinColumns' => array(<join column mapping from join table to target table>)
526
     * )
527
     * </pre>
528
     *
529
     * @var array
530
     */
531
    public $associationMappings = [];
532
533
    /**
534
     * READ-ONLY: Flag indicating whether the identifier/primary key of the class is composite.
535
     *
536
     * @var boolean
537
     */
538
    public $isIdentifierComposite = false;
539
540
    /**
541
     * READ-ONLY: Flag indicating whether the identifier/primary key contains at least one foreign key association.
542
     *
543
     * This flag is necessary because some code blocks require special treatment of this cases.
544
     *
545
     * @var boolean
546
     */
547
    public $containsForeignIdentifier = false;
548
549
    /**
550
     * READ-ONLY: The ID generator used for generating IDs for this class.
551
     *
552
     * @var \Doctrine\ORM\Id\AbstractIdGenerator
553
     *
554
     * @todo Remove!
555
     */
556
    public $idGenerator;
557
558
    /**
559
     * READ-ONLY: The definition of the sequence generator of this class. Only used for the
560
     * SEQUENCE generation strategy.
561
     *
562
     * The definition has the following structure:
563
     * <code>
564
     * array(
565
     *     'sequenceName' => 'name',
566
     *     'allocationSize' => 20,
567
     *     'initialValue' => 1
568
     * )
569
     * </code>
570
     *
571
     * @var array
572
     *
573
     * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
574
     */
575
    public $sequenceGeneratorDefinition;
576
577
    /**
578
     * READ-ONLY: The definition of the table generator of this class. Only used for the
579
     * TABLE generation strategy.
580
     *
581
     * @var array
582
     *
583
     * @todo Merge with tableGeneratorDefinition into generic generatorDefinition
584
     */
585
    public $tableGeneratorDefinition;
586
587
    /**
588
     * READ-ONLY: The policy used for change-tracking on entities of this class.
589
     *
590
     * @var integer
591
     */
592
    public $changeTrackingPolicy = self::CHANGETRACKING_DEFERRED_IMPLICIT;
593
594
    /**
595
     * READ-ONLY: A flag for whether or not instances of this class are to be versioned
596
     * with optimistic locking.
597
     *
598
     * @var boolean
599
     */
600
    public $isVersioned;
601
602
    /**
603
     * READ-ONLY: The name of the field which is used for versioning in optimistic locking (if any).
604
     *
605
     * @var mixed
606
     */
607
    public $versionField;
608
609
    /**
610
     * @var array
611
     */
612
    public $cache = null;
613
614
    /**
615
     * The ReflectionClass instance of the mapped class.
616
     *
617
     * @var ReflectionClass
618
     */
619
    public $reflClass;
620
621
    /**
622
     * Is this entity marked as "read-only"?
623
     *
624
     * That means it is never considered for change-tracking in the UnitOfWork. It is a very helpful performance
625
     * optimization for entities that are immutable, either in your domain or through the relation database
626
     * (coming from a view, or a history table for example).
627
     *
628
     * @var bool
629
     */
630
    public $isReadOnly = false;
631
632
    /**
633
     * NamingStrategy determining the default column and table names.
634
     *
635
     * @var \Doctrine\ORM\Mapping\NamingStrategy
636
     */
637
    protected $namingStrategy;
638
639
    /**
640
     * The ReflectionProperty instances of the mapped class.
641
     *
642
     * @var \ReflectionProperty[]
643
     */
644
    public $reflFields = [];
645
646
    /**
647
     * @var \Doctrine\Instantiator\InstantiatorInterface|null
648
     */
649
    private $instantiator;
650
651
    /**
652
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
653
     * metadata of the class with the given name.
654
     *
655
     * @param string              $entityName     The name of the entity class the new instance is used for.
656
     * @param NamingStrategy|null $namingStrategy
657
     */
658 753
    public function __construct($entityName, NamingStrategy $namingStrategy = null)
659
    {
660 753
        $this->name = $entityName;
661 753
        $this->rootEntityName = $entityName;
662 753
        $this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy();
663 753
        $this->instantiator   = new Instantiator();
664 753
    }
665
666
    /**
667
     * Gets the ReflectionProperties of the mapped class.
668
     *
669
     * @return array An array of ReflectionProperty instances.
670
     */
671 237
    public function getReflectionProperties()
672
    {
673 237
        return $this->reflFields;
674
    }
675
676
    /**
677
     * Gets a ReflectionProperty for a specific field of the mapped class.
678
     *
679
     * @param string $name
680
     *
681
     * @return \ReflectionProperty
682
     */
683 1
    public function getReflectionProperty($name)
684
    {
685 1
        return $this->reflFields[$name];
686
    }
687
688
    /**
689
     * Gets the ReflectionProperty for the single identifier field.
690
     *
691
     * @return \ReflectionProperty
692
     *
693
     * @throws BadMethodCallException If the class has a composite identifier.
694
     */
695
    public function getSingleIdReflectionProperty()
696
    {
697
        if ($this->isIdentifierComposite) {
698
            throw new BadMethodCallException("Class " . $this->name . " has a composite identifier.");
699
        }
700
701
        return $this->reflFields[$this->identifier[0]];
702
    }
703
704
    /**
705
     * Extracts the identifier values of an entity of this class.
706
     *
707
     * For composite identifiers, the identifier values are returned as an array
708
     * with the same order as the field order in {@link identifier}.
709
     *
710
     * @param object $entity
711
     *
712
     * @return array
713
     */
714 488
    public function getIdentifierValues($entity)
715
    {
716 488
        if ($this->isIdentifierComposite) {
717 95
            $id = [];
718
719 95
            foreach ($this->identifier as $idField) {
720 95
                $value = $this->reflFields[$idField]->getValue($entity);
721
722 95
                if (null !== $value) {
723 95
                    $id[$idField] = $value;
724
                }
725
            }
726
727 95
            return $id;
728
        }
729
730 469
        $id = $this->identifier[0];
731 469
        $value = $this->reflFields[$id]->getValue($entity);
732
733 469
        if (null === $value) {
734 31
            return [];
735
        }
736
737 443
        return [$id => $value];
738
    }
739
740
    /**
741
     * Populates the entity identifier of an entity.
742
     *
743
     * @param object $entity
744
     * @param array  $id
745
     *
746
     * @return void
747
     *
748
     * @todo Rename to assignIdentifier()
749
     */
750 8
    public function setIdentifierValues($entity, array $id)
751
    {
752 8
        foreach ($id as $idField => $idValue) {
753 8
            $this->reflFields[$idField]->setValue($entity, $idValue);
754
        }
755 8
    }
756
757
    /**
758
     * Sets the specified field to the specified value on the given entity.
759
     *
760
     * @param object $entity
761
     * @param string $field
762
     * @param mixed  $value
763
     *
764
     * @return void
765
     */
766 236
    public function setFieldValue($entity, $field, $value)
767
    {
768 236
        $this->reflFields[$field]->setValue($entity, $value);
769 236
    }
770
771
    /**
772
     * Gets the specified field's value off the given entity.
773
     *
774
     * @param object $entity
775
     * @param string $field
776
     *
777
     * @return mixed
778
     */
779 334
    public function getFieldValue($entity, $field)
780
    {
781 334
        return $this->reflFields[$field]->getValue($entity);
782
    }
783
784
    /**
785
     * Creates a string representation of this instance.
786
     *
787
     * @return string The string representation of this instance.
788
     *
789
     * @todo Construct meaningful string representation.
790
     */
791
    public function __toString()
792
    {
793
        return __CLASS__ . '@' . spl_object_hash($this);
794
    }
795
796
    /**
797
     * Determines which fields get serialized.
798
     *
799
     * It is only serialized what is necessary for best unserialization performance.
800
     * That means any metadata properties that are not set or empty or simply have
801
     * their default value are NOT serialized.
802
     *
803
     * Parts that are also NOT serialized because they can not be properly unserialized:
804
     *      - reflClass (ReflectionClass)
805
     *      - reflFields (ReflectionProperty array)
806
     *
807
     * @return array The names of all the fields that should be serialized.
808
     */
809 6
    public function __sleep()
810
    {
811
        // This metadata is always serialized/cached.
812
        $serialized = [
813 6
            'associationMappings',
814
            'columnNames', //TODO: 3.0 Remove this. Can use fieldMappings[$fieldName]['columnName']
815
            'fieldMappings',
816
            'fieldNames',
817
            'embeddedClasses',
818
            'identifier',
819
            'isIdentifierComposite', // TODO: REMOVE
820
            'name',
821
            'namespace', // TODO: REMOVE
822
            'table',
823
            'rootEntityName',
824
            'idGenerator', //TODO: Does not really need to be serialized. Could be moved to runtime.
825
        ];
826
827
        // The rest of the metadata is only serialized if necessary.
828 6
        if ($this->changeTrackingPolicy != self::CHANGETRACKING_DEFERRED_IMPLICIT) {
829
            $serialized[] = 'changeTrackingPolicy';
830
        }
831
832 6
        if ($this->customRepositoryClassName) {
833 1
            $serialized[] = 'customRepositoryClassName';
834
        }
835
836 6
        if ($this->inheritanceType != self::INHERITANCE_TYPE_NONE) {
837 1
            $serialized[] = 'inheritanceType';
838 1
            $serialized[] = 'discriminatorColumn';
839 1
            $serialized[] = 'discriminatorValue';
840 1
            $serialized[] = 'discriminatorMap';
841 1
            $serialized[] = 'parentClasses';
842 1
            $serialized[] = 'subClasses';
843
        }
844
845 6
        if ($this->generatorType != self::GENERATOR_TYPE_NONE) {
846 1
            $serialized[] = 'generatorType';
847 1
            if ($this->generatorType == self::GENERATOR_TYPE_SEQUENCE) {
848
                $serialized[] = 'sequenceGeneratorDefinition';
849
            }
850
        }
851
852 6
        if ($this->isMappedSuperclass) {
853
            $serialized[] = 'isMappedSuperclass';
854
        }
855
856 6
        if ($this->isEmbeddedClass) {
857 1
            $serialized[] = 'isEmbeddedClass';
858
        }
859
860 6
        if ($this->containsForeignIdentifier) {
861
            $serialized[] = 'containsForeignIdentifier';
862
        }
863
864 6
        if ($this->isVersioned) {
865
            $serialized[] = 'isVersioned';
866
            $serialized[] = 'versionField';
867
        }
868
869 6
        if ($this->lifecycleCallbacks) {
870
            $serialized[] = 'lifecycleCallbacks';
871
        }
872
873 6
        if ($this->entityListeners) {
874 1
            $serialized[] = 'entityListeners';
875
        }
876
877 6
        if ($this->namedQueries) {
878 1
            $serialized[] = 'namedQueries';
879
        }
880
881 6
        if ($this->namedNativeQueries) {
882
            $serialized[] = 'namedNativeQueries';
883
        }
884
885 6
        if ($this->sqlResultSetMappings) {
886
            $serialized[] = 'sqlResultSetMappings';
887
        }
888
889 6
        if ($this->isReadOnly) {
890 1
            $serialized[] = 'isReadOnly';
891
        }
892
893 6
        if ($this->customGeneratorDefinition) {
894
            $serialized[] = "customGeneratorDefinition";
895
        }
896
897 6
        if ($this->cache) {
898
            $serialized[] = 'cache';
899
        }
900
901 6
        return $serialized;
902
    }
903
904
    /**
905
     * Creates a new instance of the mapped class, without invoking the constructor.
906
     *
907
     * @return object
908
     */
909 717
    public function newInstance()
910
    {
911 717
        return $this->instantiator->instantiate($this->name);
912
    }
913
914
    /**
915
     * Restores some state that can not be serialized/unserialized.
916
     *
917
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService
918
     *
919
     * @return void
920
     */
921 2131
    public function wakeupReflection($reflService)
922
    {
923
        // Restore ReflectionClass and properties
924 2131
        $this->reflClass    = $reflService->getClass($this->name);
925 2131
        $this->instantiator = $this->instantiator ?: new Instantiator();
926
927 2131
        $parentReflFields = [];
928
929 2131
        foreach ($this->embeddedClasses as $property => $embeddedClass) {
930 23
            if (isset($embeddedClass['declaredField'])) {
931 15
                $parentReflFields[$property] = new ReflectionEmbeddedProperty(
932 15
                    $parentReflFields[$embeddedClass['declaredField']],
933 15
                    $reflService->getAccessibleProperty(
934 15
                        $this->embeddedClasses[$embeddedClass['declaredField']]['class'],
935 15
                        $embeddedClass['originalField']
936
                    ),
937 15
                    $this->embeddedClasses[$embeddedClass['declaredField']]['class']
938
                );
939
940 15
                continue;
941
            }
942
943 23
            $fieldRefl = $reflService->getAccessibleProperty(
944 23
                $embeddedClass['declared'] ?? $this->name,
945 23
                $property
946
            );
947
948 23
            $parentReflFields[$property] = $fieldRefl;
949 23
            $this->reflFields[$property] = $fieldRefl;
950
        }
951
952 2131
        foreach ($this->fieldMappings as $field => $mapping) {
953 2126
            if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
954 22
                $this->reflFields[$field] = new ReflectionEmbeddedProperty(
955 22
                    $parentReflFields[$mapping['declaredField']],
956 22
                    $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
957 22
                    $mapping['originalClass']
958
                );
959 22
                continue;
960
            }
961
962 2126
            $this->reflFields[$field] = isset($mapping['declared'])
963 546
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
964 2126
                : $reflService->getAccessibleProperty($this->name, $field);
965
        }
966
967 2131
        foreach ($this->associationMappings as $field => $mapping) {
968 1773
            $this->reflFields[$field] = isset($mapping['declared'])
969 437
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
970 1773
                : $reflService->getAccessibleProperty($this->name, $field);
971
        }
972 2131
    }
973
974
    /**
975
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
976
     * metadata of the class with the given name.
977
     *
978
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService The reflection service.
979
     *
980
     * @return void
981
     */
982 696
    public function initializeReflection($reflService)
983
    {
984 696
        $this->reflClass = $reflService->getClass($this->name);
985 696
        $this->namespace = $reflService->getClassNamespace($this->name);
986
987 696
        if ($this->reflClass) {
988 689
            $this->name = $this->rootEntityName = $this->reflClass->getName();
989
        }
990
991 696
        $this->table['name'] = $this->namingStrategy->classToTableName($this->name);
992 696
    }
993
994
    /**
995
     * Validates Identifier.
996
     *
997
     * @return void
998
     *
999
     * @throws MappingException
1000
     */
1001 471
    public function validateIdentifier()
1002
    {
1003 471
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
1004 63
            return;
1005
        }
1006
1007
        // Verify & complete identifier mapping
1008 469
        if ( ! $this->identifier) {
1009 6
            throw MappingException::identifierRequired($this->name);
1010
        }
1011
1012 463
        if ($this->usesIdGenerator() && $this->isIdentifierComposite) {
1013
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name);
1014
        }
1015 463
    }
1016
1017
    /**
1018
     * Validates association targets actually exist.
1019
     *
1020
     * @return void
1021
     *
1022
     * @throws MappingException
1023
     */
1024 472
    public function validateAssociations()
1025
    {
1026 472
        foreach ($this->associationMappings as $mapping) {
1027
            if (
1028 304
                ! class_exists($mapping['targetEntity'])
1029 304
                && ! interface_exists($mapping['targetEntity'])
1030 304
                && ! trait_exists($mapping['targetEntity'])
1031
            ) {
1032 304
                throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']);
1033
            }
1034
        }
1035 471
    }
1036
1037
    /**
1038
     * Validates lifecycle callbacks.
1039
     *
1040
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService
1041
     *
1042
     * @return void
1043
     *
1044
     * @throws MappingException
1045
     */
1046 472
    public function validateLifecycleCallbacks($reflService)
1047
    {
1048 472
        foreach ($this->lifecycleCallbacks as $callbacks) {
1049 13
            foreach ($callbacks as $callbackFuncName) {
1050 13
                if ( ! $reflService->hasPublicMethod($this->name, $callbackFuncName)) {
1051 13
                    throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName);
1052
                }
1053
            }
1054
        }
1055 471
    }
1056
1057
    /**
1058
     * {@inheritDoc}
1059
     */
1060 611
    public function getReflectionClass()
1061
    {
1062 611
        return $this->reflClass;
1063
    }
1064
1065
    /**
1066
     * @param array $cache
1067
     *
1068
     * @return void
1069
     */
1070 25
    public function enableCache(array $cache)
1071
    {
1072 25
        if ( ! isset($cache['usage'])) {
1073
            $cache['usage'] = self::CACHE_USAGE_READ_ONLY;
1074
        }
1075
1076 25
        if ( ! isset($cache['region'])) {
1077 25
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName));
1078
        }
1079
1080 25
        $this->cache = $cache;
1081 25
    }
1082
1083
    /**
1084
     * @param string $fieldName
1085
     * @param array  $cache
1086
     *
1087
     * @return void
1088
     */
1089 2
    public function enableAssociationCache($fieldName, array $cache)
1090
    {
1091 2
        $this->associationMappings[$fieldName]['cache'] = $this->getAssociationCacheDefaults($fieldName, $cache);
1092 2
    }
1093
1094
    /**
1095
     * @param string $fieldName
1096
     * @param array  $cache
1097
     *
1098
     * @return array
1099
     */
1100 19
    public function getAssociationCacheDefaults($fieldName, array $cache)
1101
    {
1102 19
        if ( ! isset($cache['usage'])) {
1103 1
            $cache['usage'] = isset($this->cache['usage'])
1104 1
                ? $this->cache['usage']
1105
                : self::CACHE_USAGE_READ_ONLY;
1106
        }
1107
1108 19
        if ( ! isset($cache['region'])) {
1109 19
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
1110
        }
1111
1112 19
        return $cache;
1113
    }
1114
1115
    /**
1116
     * Sets the change tracking policy used by this class.
1117
     *
1118
     * @param integer $policy
1119
     *
1120
     * @return void
1121
     */
1122 164
    public function setChangeTrackingPolicy($policy)
1123
    {
1124 164
        $this->changeTrackingPolicy = $policy;
1125 164
    }
1126
1127
    /**
1128
     * Whether the change tracking policy of this class is "deferred explicit".
1129
     *
1130
     * @return boolean
1131
     */
1132 275
    public function isChangeTrackingDeferredExplicit()
1133
    {
1134 275
        return self::CHANGETRACKING_DEFERRED_EXPLICIT === $this->changeTrackingPolicy;
1135
    }
1136
1137
    /**
1138
     * Whether the change tracking policy of this class is "deferred implicit".
1139
     *
1140
     * @return boolean
1141
     */
1142 485
    public function isChangeTrackingDeferredImplicit()
1143
    {
1144 485
        return self::CHANGETRACKING_DEFERRED_IMPLICIT === $this->changeTrackingPolicy;
1145
    }
1146
1147
    /**
1148
     * Whether the change tracking policy of this class is "notify".
1149
     *
1150
     * @return boolean
1151
     */
1152 313
    public function isChangeTrackingNotify()
1153
    {
1154 313
        return self::CHANGETRACKING_NOTIFY === $this->changeTrackingPolicy;
1155
    }
1156
1157
    /**
1158
     * Checks whether a field is part of the identifier/primary key field(s).
1159
     *
1160
     * @param string $fieldName The field name.
1161
     *
1162
     * @return boolean TRUE if the field is part of the table identifier/primary key field(s),
1163
     *                 FALSE otherwise.
1164
     */
1165 1153
    public function isIdentifier($fieldName)
1166
    {
1167 1153
        if ( ! $this->identifier) {
1168 1
            return false;
1169
        }
1170
1171 1152
        if ( ! $this->isIdentifierComposite) {
1172 1147
            return $fieldName === $this->identifier[0];
1173
        }
1174
1175 99
        return in_array($fieldName, $this->identifier, true);
1176
    }
1177
1178
    /**
1179
     * Checks if the field is unique.
1180
     *
1181
     * @param string $fieldName The field name.
1182
     *
1183
     * @return boolean TRUE if the field is unique, FALSE otherwise.
1184
     */
1185
    public function isUniqueField($fieldName)
1186
    {
1187
        $mapping = $this->getFieldMapping($fieldName);
1188
1189
        return false !== $mapping && isset($mapping['unique']) && $mapping['unique'];
1190
    }
1191
1192
    /**
1193
     * Checks if the field is not null.
1194
     *
1195
     * @param string $fieldName The field name.
1196
     *
1197
     * @return boolean TRUE if the field is not null, FALSE otherwise.
1198
     */
1199 1
    public function isNullable($fieldName)
1200
    {
1201 1
        $mapping = $this->getFieldMapping($fieldName);
1202
1203 1
        return false !== $mapping && isset($mapping['nullable']) && $mapping['nullable'];
1204
    }
1205
1206
    /**
1207
     * Gets a column name for a field name.
1208
     * If the column name for the field cannot be found, the given field name
1209
     * is returned.
1210
     *
1211
     * @param string $fieldName The field name.
1212
     *
1213
     * @return string The column name.
1214
     */
1215 16
    public function getColumnName($fieldName)
1216
    {
1217 16
        return isset($this->columnNames[$fieldName])
1218 16
            ? $this->columnNames[$fieldName]
1219 16
            : $fieldName;
1220
    }
1221
1222
    /**
1223
     * Gets the mapping of a (regular) field that holds some data but not a
1224
     * reference to another object.
1225
     *
1226
     * @param string $fieldName The field name.
1227
     *
1228
     * @return array The field mapping.
1229
     *
1230
     * @throws MappingException
1231
     */
1232 211
    public function getFieldMapping($fieldName)
1233
    {
1234 211
        if ( ! isset($this->fieldMappings[$fieldName])) {
1235 1
            throw MappingException::mappingNotFound($this->name, $fieldName);
1236
        }
1237
1238 210
        return $this->fieldMappings[$fieldName];
1239
    }
1240
1241
    /**
1242
     * Gets the mapping of an association.
1243
     *
1244
     * @see ClassMetadataInfo::$associationMappings
1245
     *
1246
     * @param string $fieldName The field name that represents the association in
1247
     *                          the object model.
1248
     *
1249
     * @return array The mapping.
1250
     *
1251
     * @throws MappingException
1252
     */
1253 501
    public function getAssociationMapping($fieldName)
1254
    {
1255 501
        if ( ! isset($this->associationMappings[$fieldName])) {
1256
            throw MappingException::mappingNotFound($this->name, $fieldName);
1257
        }
1258
1259 501
        return $this->associationMappings[$fieldName];
1260
    }
1261
1262
    /**
1263
     * Gets all association mappings of the class.
1264
     *
1265
     * @return array
1266
     */
1267
    public function getAssociationMappings()
1268
    {
1269
        return $this->associationMappings;
1270
    }
1271
1272
    /**
1273
     * Gets the field name for a column name.
1274
     * If no field name can be found the column name is returned.
1275
     *
1276
     * @param string $columnName The column name.
1277
     *
1278
     * @return string The column alias.
1279
     */
1280 256
    public function getFieldName($columnName)
1281
    {
1282 256
        return isset($this->fieldNames[$columnName])
1283 256
            ? $this->fieldNames[$columnName]
1284 256
            : $columnName;
1285
    }
1286
1287
    /**
1288
     * Gets the named query.
1289
     *
1290
     * @see ClassMetadataInfo::$namedQueries
1291
     *
1292
     * @param string $queryName The query name.
1293
     *
1294
     * @return string
1295
     *
1296
     * @throws MappingException
1297
     */
1298 4
    public function getNamedQuery($queryName)
1299
    {
1300 4
        if ( ! isset($this->namedQueries[$queryName])) {
1301 1
            throw MappingException::queryNotFound($this->name, $queryName);
1302
        }
1303
1304 3
        return $this->namedQueries[$queryName]['dql'];
1305
    }
1306
1307
    /**
1308
     * Gets all named queries of the class.
1309
     *
1310
     * @return array
1311
     */
1312 7
    public function getNamedQueries()
1313
    {
1314 7
        return $this->namedQueries;
1315
    }
1316
1317
    /**
1318
     * Gets the named native query.
1319
     *
1320
     * @see ClassMetadataInfo::$namedNativeQueries
1321
     *
1322
     * @param string $queryName The query name.
1323
     *
1324
     * @return array
1325
     *
1326
     * @throws MappingException
1327
     */
1328 17
    public function getNamedNativeQuery($queryName)
1329
    {
1330 17
        if ( ! isset($this->namedNativeQueries[$queryName])) {
1331
            throw MappingException::queryNotFound($this->name, $queryName);
1332
        }
1333
1334 17
        return $this->namedNativeQueries[$queryName];
1335
    }
1336
1337
    /**
1338
     * Gets all named native queries of the class.
1339
     *
1340
     * @return array
1341
     */
1342 2
    public function getNamedNativeQueries()
1343
    {
1344 2
        return $this->namedNativeQueries;
1345
    }
1346
1347
    /**
1348
     * Gets the result set mapping.
1349
     *
1350
     * @see ClassMetadataInfo::$sqlResultSetMappings
1351
     *
1352
     * @param string $name The result set mapping name.
1353
     *
1354
     * @return array
1355
     *
1356
     * @throws MappingException
1357
     */
1358 21
    public function getSqlResultSetMapping($name)
1359
    {
1360 21
        if ( ! isset($this->sqlResultSetMappings[$name])) {
1361
            throw MappingException::resultMappingNotFound($this->name, $name);
1362
        }
1363
1364 21
        return $this->sqlResultSetMappings[$name];
1365
    }
1366
1367
    /**
1368
     * Gets all sql result set mappings of the class.
1369
     *
1370
     * @return array
1371
     */
1372 8
    public function getSqlResultSetMappings()
1373
    {
1374 8
        return $this->sqlResultSetMappings;
1375
    }
1376
1377
    /**
1378
     * Validates & completes the given field mapping.
1379
     *
1380
     * @param array $mapping The field mapping to validate & complete.
1381
     *
1382
     * @return void
1383
     *
1384
     * @throws MappingException
1385
     */
1386 619
    protected function _validateAndCompleteFieldMapping(array &$mapping)
1387
    {
1388
        // Check mandatory fields
1389 619
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
1390 1
            throw MappingException::missingFieldName($this->name);
1391
        }
1392
1393 618
        if ( ! isset($mapping['type'])) {
1394
            // Default to string
1395 65
            $mapping['type'] = 'string';
1396
        }
1397
1398
        // Complete fieldName and columnName mapping
1399 618
        if ( ! isset($mapping['columnName'])) {
1400 519
            $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name);
1401
        }
1402
1403 618
        if ('`' === $mapping['columnName'][0]) {
1404 12
            $mapping['columnName']  = trim($mapping['columnName'], '`');
1405 12
            $mapping['quoted']      = true;
1406
        }
1407
1408 618
        $this->columnNames[$mapping['fieldName']] = $mapping['columnName'];
1409
1410 618
        if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorColumn && $this->discriminatorColumn['name'] === $mapping['columnName'])) {
1411 2
            throw MappingException::duplicateColumnName($this->name, $mapping['columnName']);
1412
        }
1413
1414 617
        $this->fieldNames[$mapping['columnName']] = $mapping['fieldName'];
1415
1416
        // Complete id mapping
1417 617
        if (isset($mapping['id']) && true === $mapping['id']) {
1418 578
            if ($this->versionField == $mapping['fieldName']) {
1419
                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
1420
            }
1421
1422 578
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1423 578
                $this->identifier[] = $mapping['fieldName'];
1424
            }
1425
1426
            // Check for composite key
1427 578
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1428 24
                $this->isIdentifierComposite = true;
1429
            }
1430
        }
1431
1432 617
        if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
1433 5
            if (isset($mapping['id']) && true === $mapping['id']) {
1434
                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
1435
            }
1436
1437 5
            $mapping['requireSQLConversion'] = true;
1438
        }
1439 617
    }
1440
1441
    /**
1442
     * Validates & completes the basic mapping information that is common to all
1443
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
1444
     *
1445
     * @param array $mapping The mapping.
1446
     *
1447
     * @return array The updated mapping.
1448
     *
1449
     * @throws MappingException If something is wrong with the mapping.
1450
     */
1451 395
    protected function _validateAndCompleteAssociationMapping(array $mapping)
1452
    {
1453 395
        if ( ! isset($mapping['mappedBy'])) {
1454 379
            $mapping['mappedBy'] = null;
1455
        }
1456
1457 395
        if ( ! isset($mapping['inversedBy'])) {
1458 365
            $mapping['inversedBy'] = null;
1459
        }
1460
1461 395
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
1462
1463 395
        if (empty($mapping['indexBy'])) {
1464 392
            unset($mapping['indexBy']);
1465
        }
1466
1467
        // If targetEntity is unqualified, assume it is in the same namespace as
1468
        // the sourceEntity.
1469 395
        $mapping['sourceEntity'] = $this->name;
1470
1471 395
        if (isset($mapping['targetEntity'])) {
1472 395
            $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
1473 395
            $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
1474
        }
1475
1476 395
        if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1477 1
            throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
1478
        }
1479
1480
        // Complete id mapping
1481 394
        if (isset($mapping['id']) && true === $mapping['id']) {
1482 59
            if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1483 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
1484
            }
1485
1486 58
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1487 58
                if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) {
1488
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
1489
                        $mapping['targetEntity'], $this->name, $mapping['fieldName']
1490
                    );
1491
                }
1492
1493 58
                $this->identifier[] = $mapping['fieldName'];
1494 58
                $this->containsForeignIdentifier = true;
1495
            }
1496
1497
            // Check for composite key
1498 58
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1499 28
                $this->isIdentifierComposite = true;
1500
            }
1501
1502 58
            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...
1503 3
                throw CacheException::nonCacheableEntityAssociation($this->name, $mapping['fieldName']);
1504
            }
1505
        }
1506
1507
        // Mandatory attributes for both sides
1508
        // Mandatory: fieldName, targetEntity
1509 390
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
1510
            throw MappingException::missingFieldName($this->name);
1511
        }
1512
1513 390
        if ( ! isset($mapping['targetEntity'])) {
1514
            throw MappingException::missingTargetEntity($mapping['fieldName']);
1515
        }
1516
1517
        // Mandatory and optional attributes for either side
1518 390
        if ( ! $mapping['mappedBy']) {
1519 374
            if (isset($mapping['joinTable']) && $mapping['joinTable']) {
1520 140
                if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
1521 4
                    $mapping['joinTable']['name']   = trim($mapping['joinTable']['name'], '`');
1522 374
                    $mapping['joinTable']['quoted'] = true;
1523
                }
1524
            }
1525
        } else {
1526 209
            $mapping['isOwningSide'] = false;
1527
        }
1528
1529 390
        if (isset($mapping['id']) && true === $mapping['id'] && $mapping['type'] & self::TO_MANY) {
1530 3
            throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']);
1531
        }
1532
1533
        // Fetch mode. Default fetch mode to LAZY, if not set.
1534 387
        if ( ! isset($mapping['fetch'])) {
1535 107
            $mapping['fetch'] = self::FETCH_LAZY;
1536
        }
1537
1538
        // Cascades
1539 387
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
1540
1541 387
        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1542 387
        if (in_array('all', $cascades)) {
1543 44
            $cascades = $allCascades;
1544 380
        } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
1545 1
            throw MappingException::invalidCascadeOption(
1546 1
                array_diff($cascades, $allCascades),
1547 1
                $this->name,
1548 1
                $mapping['fieldName']
1549
            );
1550
        }
1551
1552 386
        $mapping['cascade'] = $cascades;
1553 386
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1554 386
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1555 386
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1556 386
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1557 386
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1558
1559 386
        return $mapping;
1560
    }
1561
1562
    /**
1563
     * Validates & completes a one-to-one association mapping.
1564
     *
1565
     * @param array $mapping The mapping to validate & complete.
1566
     *
1567
     * @return array The validated & completed mapping.
1568
     *
1569
     * @throws RuntimeException
1570
     * @throws MappingException
1571
     */
1572 335
    protected function _validateAndCompleteOneToOneMapping(array $mapping)
1573
    {
1574 335
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1575
1576 329
        if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
1577 235
            $mapping['isOwningSide'] = true;
1578
        }
1579
1580 329
        if ($mapping['isOwningSide']) {
1581 316
            if (empty($mapping['joinColumns'])) {
1582
                // Apply default join column
1583 102
                $mapping['joinColumns'] = [
1584
                    [
1585 102
                        'name' => $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name),
1586 102
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName()
1587
                    ]
1588
                ];
1589
            }
1590
1591 316
            $uniqueConstraintColumns = [];
1592
1593 316
            foreach ($mapping['joinColumns'] as &$joinColumn) {
1594 316
                if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
1595 169
                    if (count($mapping['joinColumns']) === 1) {
1596 167
                        if (empty($mapping['id'])) {
1597 167
                            $joinColumn['unique'] = true;
1598
                        }
1599
                    } else {
1600 2
                        $uniqueConstraintColumns[] = $joinColumn['name'];
1601
                    }
1602
                }
1603
1604 316
                if (empty($joinColumn['name'])) {
1605 36
                    $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name);
1606
                }
1607
1608 316
                if (empty($joinColumn['referencedColumnName'])) {
1609 6
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1610
                }
1611
1612 316
                if ($joinColumn['name'][0] === '`') {
1613 8
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1614 8
                    $joinColumn['quoted'] = true;
1615
                }
1616
1617 316
                if ($joinColumn['referencedColumnName'][0] === '`') {
1618 5
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1619 5
                    $joinColumn['quoted']               = true;
1620
                }
1621
1622 316
                $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1623 316
                $mapping['joinColumnFieldNames'][$joinColumn['name']] = $joinColumn['fieldName'] ?? $joinColumn['name'];
1624
            }
1625
1626 316
            if ($uniqueConstraintColumns) {
1627 2
                if ( ! $this->table) {
1628
                    throw new RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.");
1629
                }
1630
1631 2
                $this->table['uniqueConstraints'][$mapping['fieldName'] . "_uniq"] = [
1632 2
                    'columns' => $uniqueConstraintColumns
1633
                ];
1634
            }
1635
1636 316
            $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
1637
        }
1638
1639 329
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1640 329
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1641
1642 329
        if ($mapping['orphanRemoval']) {
1643 22
            unset($mapping['unique']);
1644
        }
1645
1646 329
        if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
1647 2
            throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
1648
        }
1649
1650 327
        return $mapping;
1651
    }
1652
1653
    /**
1654
     * Validates & completes a one-to-many association mapping.
1655
     *
1656
     * @param array $mapping The mapping to validate and complete.
1657
     *
1658
     * @return array The validated and completed mapping.
1659
     *
1660
     * @throws MappingException
1661
     * @throws InvalidArgumentException
1662
     */
1663 150
    protected function _validateAndCompleteOneToManyMapping(array $mapping)
1664
    {
1665 150
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1666
1667
        // OneToMany-side MUST be inverse (must have mappedBy)
1668 149
        if ( ! isset($mapping['mappedBy'])) {
1669
            throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
1670
        }
1671
1672 149
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1673 149
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1674
1675 149
        $this->assertMappingOrderBy($mapping);
1676
1677 149
        return $mapping;
1678
    }
1679
1680
    /**
1681
     * Validates & completes a many-to-many association mapping.
1682
     *
1683
     * @param array $mapping The mapping to validate & complete.
1684
     *
1685
     * @return array The validated & completed mapping.
1686
     *
1687
     * @throws \InvalidArgumentException
1688
     */
1689 171
    protected function _validateAndCompleteManyToManyMapping(array $mapping)
1690
    {
1691 171
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1692
1693 169
        if ($mapping['isOwningSide']) {
1694
            // owning side MUST have a join table
1695 151
            if ( ! isset($mapping['joinTable']['name'])) {
1696 29
                $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
1697
            }
1698
1699 151
            $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
1700 151
                && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
1701
1702 151
            if ( ! isset($mapping['joinTable']['joinColumns'])) {
1703 28
                $mapping['joinTable']['joinColumns'] = [
1704
                    [
1705 28
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
1706 28
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1707 28
                        'onDelete' => 'CASCADE'
1708
                    ]
1709
                ];
1710
            }
1711
1712 151
            if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
1713 29
                $mapping['joinTable']['inverseJoinColumns'] = [
1714
                    [
1715 29
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
1716 29
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1717 29
                        'onDelete' => 'CASCADE'
1718
                    ]
1719
                ];
1720
            }
1721
1722 151
            $mapping['joinTableColumns'] = [];
1723
1724 151
            foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
1725 151
                if (empty($joinColumn['name'])) {
1726 2
                    $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
1727
                }
1728
1729 151
                if (empty($joinColumn['referencedColumnName'])) {
1730 6
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1731
                }
1732
1733 151
                if ($joinColumn['name'][0] === '`') {
1734 3
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1735 3
                    $joinColumn['quoted'] = true;
1736
                }
1737
1738 151
                if ($joinColumn['referencedColumnName'][0] === '`') {
1739 3
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1740 3
                    $joinColumn['quoted']               = true;
1741
                }
1742
1743 151
                if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') {
1744 39
                    $mapping['isOnDeleteCascade'] = true;
1745
                }
1746
1747 151
                $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1748 151
                $mapping['joinTableColumns'][] = $joinColumn['name'];
1749
            }
1750
1751 151
            foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) {
1752 151
                if (empty($inverseJoinColumn['name'])) {
1753 2
                    $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']);
1754
                }
1755
1756 151
                if (empty($inverseJoinColumn['referencedColumnName'])) {
1757 6
                    $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1758
                }
1759
1760 151
                if ($inverseJoinColumn['name'][0] === '`') {
1761 3
                    $inverseJoinColumn['name']   = trim($inverseJoinColumn['name'], '`');
1762 3
                    $inverseJoinColumn['quoted'] = true;
1763
                }
1764
1765 151
                if ($inverseJoinColumn['referencedColumnName'][0] === '`') {
1766 3
                    $inverseJoinColumn['referencedColumnName']  = trim($inverseJoinColumn['referencedColumnName'], '`');
1767 3
                    $inverseJoinColumn['quoted']                = true;
1768
                }
1769
1770 151
                if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
1771 35
                    $mapping['isOnDeleteCascade'] = true;
1772
                }
1773
1774 151
                $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
1775 151
                $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
1776
            }
1777
        }
1778
1779 169
        $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1780
1781 169
        $this->assertMappingOrderBy($mapping);
1782
1783 169
        return $mapping;
1784
    }
1785
1786
    /**
1787
     * {@inheritDoc}
1788
     */
1789 645
    public function getIdentifierFieldNames()
1790
    {
1791 645
        return $this->identifier;
1792
    }
1793
1794
    /**
1795
     * Gets the name of the single id field. Note that this only works on
1796
     * entity classes that have a single-field pk.
1797
     *
1798
     * @return string
1799
     *
1800
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1801
     */
1802 1215
    public function getSingleIdentifierFieldName()
1803
    {
1804 1215
        if ($this->isIdentifierComposite) {
1805 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
1806
        }
1807
1808 1214
        if ( ! isset($this->identifier[0])) {
1809 1
            throw MappingException::noIdDefined($this->name);
1810
        }
1811
1812 1213
        return $this->identifier[0];
1813
    }
1814
1815
    /**
1816
     * Gets the column name of the single id column. Note that this only works on
1817
     * entity classes that have a single-field pk.
1818
     *
1819
     * @return string
1820
     *
1821
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1822
     */
1823 3
    public function getSingleIdentifierColumnName()
1824
    {
1825 3
        return $this->getColumnName($this->getSingleIdentifierFieldName());
1826
    }
1827
1828
    /**
1829
     * INTERNAL:
1830
     * Sets the mapped identifier/primary key fields of this class.
1831
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
1832
     *
1833
     * @param array $identifier
1834
     *
1835
     * @return void
1836
     */
1837 147
    public function setIdentifier(array $identifier)
1838
    {
1839 147
        $this->identifier = $identifier;
1840 147
        $this->isIdentifierComposite = (count($this->identifier) > 1);
1841 147
    }
1842
1843
    /**
1844
     * {@inheritDoc}
1845
     */
1846 65
    public function getIdentifier()
1847
    {
1848 65
        return $this->identifier;
1849
    }
1850
1851
    /**
1852
     * {@inheritDoc}
1853
     */
1854 313
    public function hasField($fieldName)
1855
    {
1856 313
        return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
1857
    }
1858
1859
    /**
1860
     * Gets an array containing all the column names.
1861
     *
1862
     * @param array|null $fieldNames
1863
     *
1864
     * @return array
1865
     */
1866 49
    public function getColumnNames(array $fieldNames = null)
1867
    {
1868 49
        if (null === $fieldNames) {
1869 48
            return array_keys($this->fieldNames);
1870
        }
1871
1872 1
        return array_values(array_map([$this, 'getColumnName'], $fieldNames));
1873
    }
1874
1875
    /**
1876
     * Returns an array with all the identifier column names.
1877
     *
1878
     * @return array
1879
     */
1880 348
    public function getIdentifierColumnNames()
1881
    {
1882 348
        $columnNames = [];
1883
1884 348
        foreach ($this->identifier as $idProperty) {
1885 348
            if (isset($this->fieldMappings[$idProperty])) {
1886 343
                $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
1887
1888 343
                continue;
1889
            }
1890
1891
            // Association defined as Id field
1892 23
            $joinColumns      = $this->associationMappings[$idProperty]['joinColumns'];
1893
            $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
1894
1895 23
            $columnNames = array_merge($columnNames, $assocColumnNames);
1896
        }
1897
1898 348
        return $columnNames;
1899
    }
1900
1901
    /**
1902
     * Sets the type of Id generator to use for the mapped class.
1903
     *
1904
     * @param int $generatorType
1905
     *
1906
     * @return void
1907
     */
1908 529
    public function setIdGeneratorType($generatorType)
1909
    {
1910 529
        $this->generatorType = $generatorType;
1911 529
    }
1912
1913
    /**
1914
     * Checks whether the mapped class uses an Id generator.
1915
     *
1916
     * @return boolean TRUE if the mapped class uses an Id generator, FALSE otherwise.
1917
     */
1918 463
    public function usesIdGenerator()
1919
    {
1920 463
        return $this->generatorType != self::GENERATOR_TYPE_NONE;
1921
    }
1922
1923
    /**
1924
     * @return boolean
1925
     */
1926 1435
    public function isInheritanceTypeNone()
1927
    {
1928 1435
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1929
    }
1930
1931
    /**
1932
     * Checks whether the mapped class uses the JOINED inheritance mapping strategy.
1933
     *
1934
     * @return boolean TRUE if the class participates in a JOINED inheritance mapping,
1935
     *                 FALSE otherwise.
1936
     */
1937 1117
    public function isInheritanceTypeJoined()
1938
    {
1939 1117
        return $this->inheritanceType == self::INHERITANCE_TYPE_JOINED;
1940
    }
1941
1942
    /**
1943
     * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy.
1944
     *
1945
     * @return boolean TRUE if the class participates in a SINGLE_TABLE inheritance mapping,
1946
     *                 FALSE otherwise.
1947
     */
1948 1320
    public function isInheritanceTypeSingleTable()
1949
    {
1950 1320
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE;
1951
    }
1952
1953
    /**
1954
     * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy.
1955
     *
1956
     * @return boolean TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping,
1957
     *                 FALSE otherwise.
1958
     */
1959 281
    public function isInheritanceTypeTablePerClass()
1960
    {
1961 281
        return $this->inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
1962
    }
1963
1964
    /**
1965
     * Checks whether the class uses an identity column for the Id generation.
1966
     *
1967
     * @return boolean TRUE if the class uses the IDENTITY generator, FALSE otherwise.
1968
     */
1969 1143
    public function isIdGeneratorIdentity()
1970
    {
1971 1143
        return $this->generatorType == self::GENERATOR_TYPE_IDENTITY;
1972
    }
1973
1974
    /**
1975
     * Checks whether the class uses a sequence for id generation.
1976
     *
1977
     * @return boolean TRUE if the class uses the SEQUENCE generator, FALSE otherwise.
1978
     */
1979 353
    public function isIdGeneratorSequence()
1980
    {
1981 353
        return $this->generatorType == self::GENERATOR_TYPE_SEQUENCE;
1982
    }
1983
1984
    /**
1985
     * Checks whether the class uses a table for id generation.
1986
     *
1987
     * @return boolean TRUE if the class uses the TABLE generator, FALSE otherwise.
1988
     */
1989 101
    public function isIdGeneratorTable()
1990
    {
1991 101
        return $this->generatorType == self::GENERATOR_TYPE_TABLE;
1992
    }
1993
1994
    /**
1995
     * Checks whether the class has a natural identifier/pk (which means it does
1996
     * not use any Id generator.
1997
     *
1998
     * @return boolean
1999
     */
2000 75
    public function isIdentifierNatural()
2001
    {
2002 75
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
2003
    }
2004
2005
    /**
2006
     * Checks whether the class use a UUID for id generation.
2007
     *
2008
     * @return boolean
2009
     */
2010
    public function isIdentifierUuid()
2011
    {
2012
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
2013
    }
2014
2015
    /**
2016
     * Gets the type of a field.
2017
     *
2018
     * @param string $fieldName
2019
     *
2020
     * @return \Doctrine\DBAL\Types\Type|string|null
2021
     *
2022
     * @todo 3.0 Remove this. PersisterHelper should fix it somehow
2023
     */
2024 979
    public function getTypeOfField($fieldName)
2025
    {
2026 979
        return isset($this->fieldMappings[$fieldName])
2027 979
            ? $this->fieldMappings[$fieldName]['type']
2028 979
            : null;
2029
    }
2030
2031
    /**
2032
     * Gets the type of a column.
2033
     *
2034
     * @param string $columnName
2035
     *
2036
     * @return \Doctrine\DBAL\Types\Type|string|null
2037
     *
2038
     * @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
2039
     *             that is derived by a referenced field on a different entity.
2040
     */
2041
    public function getTypeOfColumn($columnName)
2042
    {
2043
        return $this->getTypeOfField($this->getFieldName($columnName));
2044
    }
2045
2046
    /**
2047
     * Gets the name of the primary table.
2048
     *
2049
     * @return string
2050
     */
2051 1429
    public function getTableName()
2052
    {
2053 1429
        return $this->table['name'];
2054
    }
2055
2056
    /**
2057
     * Gets primary table's schema name.
2058
     *
2059
     * @return string|null
2060
     */
2061 13
    public function getSchemaName()
2062
    {
2063 13
        return isset($this->table['schema']) ? $this->table['schema'] : null;
2064
    }
2065
2066
    /**
2067
     * Gets the table name to use for temporary identifier tables of this class.
2068
     *
2069
     * @return string
2070
     */
2071 7
    public function getTemporaryIdTableName()
2072
    {
2073
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
2074 7
        return str_replace('.', '_', $this->getTableName() . '_id_tmp');
2075
    }
2076
2077
    /**
2078
     * Sets the mapped subclasses of this class.
2079
     *
2080
     * @param array $subclasses The names of all mapped subclasses.
2081
     *
2082
     * @return void
2083
     */
2084 2
    public function setSubclasses(array $subclasses)
2085
    {
2086 2
        foreach ($subclasses as $subclass) {
2087 2
            $this->subClasses[] = $this->fullyQualifiedClassName($subclass);
2088
        }
2089 2
    }
2090
2091
    /**
2092
     * Sets the parent class names.
2093
     * Assumes that the class names in the passed array are in the order:
2094
     * directParent -> directParentParent -> directParentParentParent ... -> root.
2095
     *
2096
     * @param array $classNames
2097
     *
2098
     * @return void
2099
     */
2100 480
    public function setParentClasses(array $classNames)
2101
    {
2102 480
        $this->parentClasses = $classNames;
2103
2104 480
        if (count($classNames) > 0) {
2105 100
            $this->rootEntityName = array_pop($classNames);
2106
        }
2107 480
    }
2108
2109
    /**
2110
     * Sets the inheritance type used by the class and its subclasses.
2111
     *
2112
     * @param integer $type
2113
     *
2114
     * @return void
2115
     *
2116
     * @throws MappingException
2117
     */
2118 197
    public function setInheritanceType($type)
2119
    {
2120 197
        if ( ! $this->_isInheritanceType($type)) {
2121
            throw MappingException::invalidInheritanceType($this->name, $type);
2122
        }
2123
2124 197
        $this->inheritanceType = $type;
2125 197
    }
2126
2127
    /**
2128
     * Sets the association to override association mapping of property for an entity relationship.
2129
     *
2130
     * @param string $fieldName
2131
     * @param array  $overrideMapping
2132
     *
2133
     * @return void
2134
     *
2135
     * @throws MappingException
2136
     */
2137 26
    public function setAssociationOverride($fieldName, array $overrideMapping)
2138
    {
2139 26
        if ( ! isset($this->associationMappings[$fieldName])) {
2140 1
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2141
        }
2142
2143 25
        $mapping = $this->associationMappings[$fieldName];
2144
2145 25
        if (isset($overrideMapping['joinColumns'])) {
2146 13
            $mapping['joinColumns'] = $overrideMapping['joinColumns'];
2147
        }
2148
2149 25
        if (isset($overrideMapping['inversedBy'])) {
2150 6
            $mapping['inversedBy'] = $overrideMapping['inversedBy'];
2151
        }
2152
2153 25
        if (isset($overrideMapping['joinTable'])) {
2154 12
            $mapping['joinTable'] = $overrideMapping['joinTable'];
2155
        }
2156
2157 25
        if (isset($overrideMapping['fetch'])) {
2158 6
            $mapping['fetch'] = $overrideMapping['fetch'];
2159
        }
2160
2161 25
        $mapping['joinColumnFieldNames']        = null;
2162 25
        $mapping['joinTableColumns']            = null;
2163 25
        $mapping['sourceToTargetKeyColumns']    = null;
2164 25
        $mapping['relationToSourceKeyColumns']  = null;
2165 25
        $mapping['relationToTargetKeyColumns']  = null;
2166
2167 25
        switch ($mapping['type']) {
2168 25
            case self::ONE_TO_ONE:
2169 1
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2170 1
                break;
2171 24
            case self::ONE_TO_MANY:
2172
                $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2173
                break;
2174 24
            case self::MANY_TO_ONE:
2175 12
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2176 12
                break;
2177 24
            case self::MANY_TO_MANY:
2178 24
                $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2179 24
                break;
2180
        }
2181
2182 25
        $this->associationMappings[$fieldName] = $mapping;
2183 25
    }
2184
2185
    /**
2186
     * Sets the override for a mapped field.
2187
     *
2188
     * @param string $fieldName
2189
     * @param array  $overrideMapping
2190
     *
2191
     * @return void
2192
     *
2193
     * @throws MappingException
2194
     */
2195 15
    public function setAttributeOverride($fieldName, array $overrideMapping)
2196
    {
2197 15
        if ( ! isset($this->fieldMappings[$fieldName])) {
2198 1
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2199
        }
2200
2201 14
        $mapping = $this->fieldMappings[$fieldName];
2202
2203 14
        if (isset($mapping['id'])) {
2204 12
            $overrideMapping['id'] = $mapping['id'];
2205
        }
2206
2207 14
        if ( ! isset($overrideMapping['type'])) {
2208 6
            $overrideMapping['type'] = $mapping['type'];
2209
        }
2210
2211 14
        if ( ! isset($overrideMapping['fieldName'])) {
2212 5
            $overrideMapping['fieldName'] = $mapping['fieldName'];
2213
        }
2214
2215 14
        if ($overrideMapping['type'] !== $mapping['type']) {
2216 1
            throw MappingException::invalidOverrideFieldType($this->name, $fieldName);
2217
        }
2218
2219 13
        unset($this->fieldMappings[$fieldName]);
2220 13
        unset($this->fieldNames[$mapping['columnName']]);
2221 13
        unset($this->columnNames[$mapping['fieldName']]);
2222
2223 13
        $this->_validateAndCompleteFieldMapping($overrideMapping);
2224
2225 13
        $this->fieldMappings[$fieldName] = $overrideMapping;
2226 13
    }
2227
2228
    /**
2229
     * Checks whether a mapped field is inherited from an entity superclass.
2230
     *
2231
     * @param string $fieldName
2232
     *
2233
     * @return bool TRUE if the field is inherited, FALSE otherwise.
2234
     */
2235 449
    public function isInheritedField($fieldName)
2236
    {
2237 449
        return isset($this->fieldMappings[$fieldName]['inherited']);
2238
    }
2239
2240
    /**
2241
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
2242
     *
2243
     * @return bool
2244
     */
2245 479
    public function isRootEntity()
2246
    {
2247 479
        return $this->name == $this->rootEntityName;
2248
    }
2249
2250
    /**
2251
     * Checks whether a mapped association field is inherited from a superclass.
2252
     *
2253
     * @param string $fieldName
2254
     *
2255
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
2256
     */
2257 414
    public function isInheritedAssociation($fieldName)
2258
    {
2259 414
        return isset($this->associationMappings[$fieldName]['inherited']);
2260
    }
2261
2262 414
    public function isInheritedEmbeddedClass($fieldName)
2263
    {
2264 414
        return isset($this->embeddedClasses[$fieldName]['inherited']);
2265
    }
2266
2267
    /**
2268
     * Sets the name of the primary table the class is mapped to.
2269
     *
2270
     * @param string $tableName The table name.
2271
     *
2272
     * @return void
2273
     *
2274
     * @deprecated Use {@link setPrimaryTable}.
2275
     */
2276 5
    public function setTableName($tableName)
2277
    {
2278 5
        $this->table['name'] = $tableName;
2279 5
    }
2280
2281
    /**
2282
     * Sets the primary table definition. The provided array supports the
2283
     * following structure:
2284
     *
2285
     * name => <tableName> (optional, defaults to class name)
2286
     * indexes => array of indexes (optional)
2287
     * uniqueConstraints => array of constraints (optional)
2288
     *
2289
     * If a key is omitted, the current value is kept.
2290
     *
2291
     * @param array $table The table description.
2292
     *
2293
     * @return void
2294
     */
2295 362
    public function setPrimaryTable(array $table)
2296
    {
2297 362
        if (isset($table['name'])) {
2298
            // Split schema and table name from a table name like "myschema.mytable"
2299 293
            if (strpos($table['name'], '.') !== false) {
2300 9
                list($this->table['schema'], $table['name']) = explode('.', $table['name'], 2);
2301
            }
2302
2303 293
            if ($table['name'][0] === '`') {
2304 18
                $table['name']          = trim($table['name'], '`');
2305 18
                $this->table['quoted']  = true;
2306
            }
2307
2308 293
            $this->table['name'] = $table['name'];
2309
        }
2310
2311 362
        if (isset($table['quoted'])) {
2312 2
            $this->table['quoted'] = $table['quoted'];
2313
        }
2314
2315 362
        if (isset($table['schema'])) {
2316 6
            $this->table['schema'] = $table['schema'];
2317
        }
2318
2319 362
        if (isset($table['indexes'])) {
2320 18
            $this->table['indexes'] = $table['indexes'];
2321
        }
2322
2323 362
        if (isset($table['uniqueConstraints'])) {
2324 9
            $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
2325
        }
2326
2327 362
        if (isset($table['options'])) {
2328 11
            $this->table['options'] = $table['options'];
2329
        }
2330 362
    }
2331
2332
    /**
2333
     * Checks whether the given type identifies an inheritance type.
2334
     *
2335
     * @param integer $type
2336
     *
2337
     * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise.
2338
     */
2339 197
    private function _isInheritanceType($type)
2340
    {
2341 197
        return $type == self::INHERITANCE_TYPE_NONE ||
2342 124
                $type == self::INHERITANCE_TYPE_SINGLE_TABLE ||
2343 64
                $type == self::INHERITANCE_TYPE_JOINED ||
2344 197
                $type == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
2345
    }
2346
2347
    /**
2348
     * Adds a mapped field to the class.
2349
     *
2350
     * @param array $mapping The field mapping.
2351
     *
2352
     * @return void
2353
     *
2354
     * @throws MappingException
2355
     */
2356 619
    public function mapField(array $mapping)
2357
    {
2358 619
        $this->_validateAndCompleteFieldMapping($mapping);
2359 617
        $this->assertFieldNotMapped($mapping['fieldName']);
2360
2361 616
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2362 616
    }
2363
2364
    /**
2365
     * INTERNAL:
2366
     * Adds an association mapping without completing/validating it.
2367
     * This is mainly used to add inherited association mappings to derived classes.
2368
     *
2369
     * @param array $mapping
2370
     *
2371
     * @return void
2372
     *
2373
     * @throws MappingException
2374
     */
2375 59
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
2376
    {
2377 59
        if (isset($this->associationMappings[$mapping['fieldName']])) {
2378 1
            throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']);
2379
        }
2380 59
        $this->associationMappings[$mapping['fieldName']] = $mapping;
2381 59
    }
2382
2383
    /**
2384
     * INTERNAL:
2385
     * Adds a field mapping without completing/validating it.
2386
     * This is mainly used to add inherited field mappings to derived classes.
2387
     *
2388
     * @param array $fieldMapping
2389
     *
2390
     * @return void
2391
     */
2392 131
    public function addInheritedFieldMapping(array $fieldMapping)
2393
    {
2394 131
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
2395 131
        $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
2396 131
        $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
2397 131
    }
2398
2399
    /**
2400
     * INTERNAL:
2401
     * Adds a named query to this class.
2402
     *
2403
     * @param array $queryMapping
2404
     *
2405
     * @return void
2406
     *
2407
     * @throws MappingException
2408
     */
2409 36
    public function addNamedQuery(array $queryMapping)
2410
    {
2411 36
        if (!isset($queryMapping['name'])) {
2412 2
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2413
        }
2414
2415 34
        if (isset($this->namedQueries[$queryMapping['name']])) {
2416 1
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2417
        }
2418
2419 34
        if (!isset($queryMapping['query'])) {
2420
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2421
        }
2422
2423 34
        $name   = $queryMapping['name'];
2424 34
        $query  = $queryMapping['query'];
2425 34
        $dql    = str_replace('__CLASS__', $this->name, $query);
2426
2427 34
        $this->namedQueries[$name] = [
2428 34
            'name'  => $name,
2429 34
            'query' => $query,
2430 34
            'dql'   => $dql,
2431
        ];
2432 34
    }
2433
2434
    /**
2435
     * INTERNAL:
2436
     * Adds a named native query to this class.
2437
     *
2438
     * @param array $queryMapping
2439
     *
2440
     * @return void
2441
     *
2442
     * @throws MappingException
2443
     */
2444 41
    public function addNamedNativeQuery(array $queryMapping)
2445
    {
2446 41
        if (!isset($queryMapping['name'])) {
2447
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2448
        }
2449
2450 41
        if (isset($this->namedNativeQueries[$queryMapping['name']])) {
2451 1
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2452
        }
2453
2454 41
        if (!isset($queryMapping['query'])) {
2455
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2456
        }
2457
2458 41
        if (!isset($queryMapping['resultClass']) && !isset($queryMapping['resultSetMapping'])) {
2459
            throw MappingException::missingQueryMapping($this->name, $queryMapping['name']);
2460
        }
2461
2462 41
        $queryMapping['isSelfClass'] = false;
2463
2464 41
        if (isset($queryMapping['resultClass'])) {
2465 39
            if ($queryMapping['resultClass'] === '__CLASS__') {
2466
2467 13
                $queryMapping['isSelfClass'] = true;
2468 13
                $queryMapping['resultClass'] = $this->name;
2469
            }
2470
2471 39
            $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
2472 39
            $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\');
2473
        }
2474
2475 41
        $this->namedNativeQueries[$queryMapping['name']] = $queryMapping;
2476 41
    }
2477
2478
    /**
2479
     * INTERNAL:
2480
     * Adds a sql result set mapping to this class.
2481
     *
2482
     * @param array $resultMapping
2483
     *
2484
     * @return void
2485
     *
2486
     * @throws MappingException
2487
     */
2488 41
    public function addSqlResultSetMapping(array $resultMapping)
2489
    {
2490 41
        if (!isset($resultMapping['name'])) {
2491
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name);
2492
        }
2493
2494 41
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
2495 1
            throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']);
2496
        }
2497
2498 41
        if (isset($resultMapping['entities'])) {
2499 41
            foreach ($resultMapping['entities'] as $key => $entityResult) {
2500 41
                if (!isset($entityResult['entityClass'])) {
2501 1
                    throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']);
2502
                }
2503
2504 40
                $entityResult['isSelfClass'] = false;
2505 40
                if ($entityResult['entityClass'] === '__CLASS__') {
2506
2507 23
                    $entityResult['isSelfClass'] = true;
2508 23
                    $entityResult['entityClass'] = $this->name;
2509
2510
                }
2511
2512 40
                $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']);
2513
2514 40
                $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
2515 40
                $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
2516
2517 40
                if (isset($entityResult['fields'])) {
2518 36
                    foreach ($entityResult['fields'] as $k => $field) {
2519 36
                        if (!isset($field['name'])) {
2520
                            throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']);
2521
                        }
2522
2523 36
                        if (!isset($field['column'])) {
2524 17
                            $fieldName = $field['name'];
2525 17
                            if (strpos($fieldName, '.')) {
2526 10
                                list(, $fieldName) = explode('.', $fieldName);
2527
                            }
2528
2529 40
                            $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
2530
                        }
2531
                    }
2532
                }
2533
            }
2534
        }
2535
2536 40
        $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
2537 40
    }
2538
2539
    /**
2540
     * Adds a one-to-one mapping.
2541
     *
2542
     * @param array $mapping The mapping.
2543
     *
2544
     * @return void
2545
     */
2546 188
    public function mapOneToOne(array $mapping)
2547
    {
2548 188
        $mapping['type'] = self::ONE_TO_ONE;
2549
2550 188
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2551
2552 185
        $this->_storeAssociationMapping($mapping);
2553 184
    }
2554
2555
    /**
2556
     * Adds a one-to-many mapping.
2557
     *
2558
     * @param array $mapping The mapping.
2559
     *
2560
     * @return void
2561
     */
2562 150
    public function mapOneToMany(array $mapping)
2563
    {
2564 150
        $mapping['type'] = self::ONE_TO_MANY;
2565
2566 150
        $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2567
2568 149
        $this->_storeAssociationMapping($mapping);
2569 149
    }
2570
2571
    /**
2572
     * Adds a many-to-one mapping.
2573
     *
2574
     * @param array $mapping The mapping.
2575
     *
2576
     * @return void
2577
     */
2578 182
    public function mapManyToOne(array $mapping)
2579
    {
2580 182
        $mapping['type'] = self::MANY_TO_ONE;
2581
2582
        // A many-to-one mapping is essentially a one-one backreference
2583 182
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2584
2585 177
        $this->_storeAssociationMapping($mapping);
2586 177
    }
2587
2588
    /**
2589
     * Adds a many-to-many mapping.
2590
     *
2591
     * @param array $mapping The mapping.
2592
     *
2593
     * @return void
2594
     */
2595 171
    public function mapManyToMany(array $mapping)
2596
    {
2597 171
        $mapping['type'] = self::MANY_TO_MANY;
2598
2599 171
        $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2600
2601 169
        $this->_storeAssociationMapping($mapping);
2602 169
    }
2603
2604
    /**
2605
     * Stores the association mapping.
2606
     *
2607
     * @param array $assocMapping
2608
     *
2609
     * @return void
2610
     *
2611
     * @throws MappingException
2612
     */
2613 384
    protected function _storeAssociationMapping(array $assocMapping)
2614
    {
2615 384
        $sourceFieldName = $assocMapping['fieldName'];
2616
2617 384
        $this->assertFieldNotMapped($sourceFieldName);
2618
2619 383
        $this->associationMappings[$sourceFieldName] = $assocMapping;
2620 383
    }
2621
2622
    /**
2623
     * Registers a custom repository class for the entity class.
2624
     *
2625
     * @param string $repositoryClassName The class name of the custom mapper.
2626
     *
2627
     * @return void
2628
     */
2629 65
    public function setCustomRepositoryClass($repositoryClassName)
2630
    {
2631 65
        $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
2632 65
    }
2633
2634
    /**
2635
     * Dispatches the lifecycle event of the given entity to the registered
2636
     * lifecycle callbacks and lifecycle listeners.
2637
     *
2638
     * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker
2639
     *
2640
     * @param string $lifecycleEvent The lifecycle event.
2641
     * @param object $entity         The Entity on which the event occurred.
2642
     *
2643
     * @return void
2644
     */
2645 1
    public function invokeLifecycleCallbacks($lifecycleEvent, $entity)
2646
    {
2647 1
        foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) {
2648 1
            $entity->$callback();
2649
        }
2650 1
    }
2651
2652
    /**
2653
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
2654
     *
2655
     * @param string $lifecycleEvent
2656
     *
2657
     * @return boolean
2658
     */
2659
    public function hasLifecycleCallbacks($lifecycleEvent)
2660
    {
2661
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
2662
    }
2663
2664
    /**
2665
     * Gets the registered lifecycle callbacks for an event.
2666
     *
2667
     * @param string $event
2668
     *
2669
     * @return array
2670
     */
2671
    public function getLifecycleCallbacks($event)
2672
    {
2673
        return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : [];
2674
    }
2675
2676
    /**
2677
     * Adds a lifecycle callback for entities of this class.
2678
     *
2679
     * @param string $callback
2680
     * @param string $event
2681
     *
2682
     * @return void
2683
     */
2684 47
    public function addLifecycleCallback($callback, $event)
2685
    {
2686 47
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
2687 3
            return;
2688
        }
2689
2690 47
        $this->lifecycleCallbacks[$event][] = $callback;
2691 47
    }
2692
2693
    /**
2694
     * Sets the lifecycle callbacks for entities of this class.
2695
     * Any previously registered callbacks are overwritten.
2696
     *
2697
     * @param array $callbacks
2698
     *
2699
     * @return void
2700
     */
2701 146
    public function setLifecycleCallbacks(array $callbacks)
2702
    {
2703 146
        $this->lifecycleCallbacks = $callbacks;
2704 146
    }
2705
2706
    /**
2707
     * Adds a entity listener for entities of this class.
2708
     *
2709
     * @param string $eventName The entity lifecycle event.
2710
     * @param string $class     The listener class.
2711
     * @param string $method    The listener callback method.
2712
     *
2713
     * @throws \Doctrine\ORM\Mapping\MappingException
2714
     */
2715 43
    public function addEntityListener($eventName, $class, $method)
2716
    {
2717 43
        $class    = $this->fullyQualifiedClassName($class);
2718
2719
        $listener = [
2720 43
            'class'  => $class,
2721 43
            'method' => $method,
2722
        ];
2723
2724 43
        if ( ! class_exists($class)) {
2725 1
            throw MappingException::entityListenerClassNotFound($class, $this->name);
2726
        }
2727
2728 42
        if ( ! method_exists($class, $method)) {
2729 1
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->name);
2730
        }
2731
2732 41
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName])) {
2733 1
            throw MappingException::duplicateEntityListener($class, $method, $this->name);
2734
        }
2735
2736 41
        $this->entityListeners[$eventName][] = $listener;
2737 41
    }
2738
2739
    /**
2740
     * Sets the discriminator column definition.
2741
     *
2742
     * @param array $columnDef
2743
     *
2744
     * @return void
2745
     *
2746
     * @throws MappingException
2747
     *
2748
     * @see getDiscriminatorColumn()
2749
     */
2750 189
    public function setDiscriminatorColumn($columnDef)
2751
    {
2752 189
        if ($columnDef !== null) {
2753 128
            if ( ! isset($columnDef['name'])) {
2754 1
                throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name);
2755
            }
2756
2757 127
            if (isset($this->fieldNames[$columnDef['name']])) {
2758 1
                throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
2759
            }
2760
2761 126
            if ( ! isset($columnDef['fieldName'])) {
2762 122
                $columnDef['fieldName'] = $columnDef['name'];
2763
            }
2764
2765 126
            if ( ! isset($columnDef['type'])) {
2766 2
                $columnDef['type'] = "string";
2767
            }
2768
2769 126
            if (in_array($columnDef['type'], ["boolean", "array", "object", "datetime", "time", "date"])) {
2770
                throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
2771
            }
2772
2773 126
            $this->discriminatorColumn = $columnDef;
2774
        }
2775 187
    }
2776
2777
    /**
2778
     * Sets the discriminator values used by this class.
2779
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
2780
     *
2781
     * @param array $map
2782
     *
2783
     * @return void
2784
     */
2785 181
    public function setDiscriminatorMap(array $map)
2786
    {
2787 181
        foreach ($map as $value => $className) {
2788 121
            $this->addDiscriminatorMapClass($value, $className);
2789
        }
2790 181
    }
2791
2792
    /**
2793
     * Adds one entry of the discriminator map with a new class and corresponding name.
2794
     *
2795
     * @param string $name
2796
     * @param string $className
2797
     *
2798
     * @return void
2799
     *
2800
     * @throws MappingException
2801
     */
2802 122
    public function addDiscriminatorMapClass($name, $className)
2803
    {
2804 122
        $className = $this->fullyQualifiedClassName($className);
2805 122
        $className = ltrim($className, '\\');
2806
2807 122
        $this->discriminatorMap[$name] = $className;
2808
2809 122
        if ($this->name === $className) {
2810 93
            $this->discriminatorValue = $name;
2811
2812 93
            return;
2813
        }
2814
2815 121
        if ( ! (class_exists($className) || interface_exists($className))) {
2816
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
2817
        }
2818
2819 121
        if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) {
2820 111
            $this->subClasses[] = $className;
2821
        }
2822 121
    }
2823
2824
    /**
2825
     * Checks whether the class has a named query with the given query name.
2826
     *
2827
     * @param string $queryName
2828
     *
2829
     * @return boolean
2830
     */
2831 1
    public function hasNamedQuery($queryName)
2832
    {
2833 1
        return isset($this->namedQueries[$queryName]);
2834
    }
2835
2836
    /**
2837
     * Checks whether the class has a named native query with the given query name.
2838
     *
2839
     * @param string $queryName
2840
     *
2841
     * @return boolean
2842
     */
2843 1
    public function hasNamedNativeQuery($queryName)
2844
    {
2845 1
        return isset($this->namedNativeQueries[$queryName]);
2846
    }
2847
2848
    /**
2849
     * Checks whether the class has a named native query with the given query name.
2850
     *
2851
     * @param string $name
2852
     *
2853
     * @return boolean
2854
     */
2855 1
    public function hasSqlResultSetMapping($name)
2856
    {
2857 1
        return isset($this->sqlResultSetMappings[$name]);
2858
    }
2859
2860
    /**
2861
     * {@inheritDoc}
2862
     */
2863 352
    public function hasAssociation($fieldName)
2864
    {
2865 352
        return isset($this->associationMappings[$fieldName]);
2866
    }
2867
2868
    /**
2869
     * {@inheritDoc}
2870
     */
2871 1
    public function isSingleValuedAssociation($fieldName)
2872
    {
2873 1
        return isset($this->associationMappings[$fieldName])
2874 1
            && ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2875
    }
2876
2877
    /**
2878
     * {@inheritDoc}
2879
     */
2880 1099
    public function isCollectionValuedAssociation($fieldName)
2881
    {
2882 1099
        return isset($this->associationMappings[$fieldName])
2883 1099
            && ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2884
    }
2885
2886
    /**
2887
     * Is this an association that only has a single join column?
2888
     *
2889
     * @param string $fieldName
2890
     *
2891
     * @return bool
2892
     */
2893 99
    public function isAssociationWithSingleJoinColumn($fieldName)
2894
    {
2895 99
        return isset($this->associationMappings[$fieldName])
2896 99
            && isset($this->associationMappings[$fieldName]['joinColumns'][0])
2897 99
            && ! isset($this->associationMappings[$fieldName]['joinColumns'][1]);
2898
    }
2899
2900
    /**
2901
     * Returns the single association join column (if any).
2902
     *
2903
     * @param string $fieldName
2904
     *
2905
     * @return string
2906
     *
2907
     * @throws MappingException
2908
     */
2909 10
    public function getSingleAssociationJoinColumnName($fieldName)
2910
    {
2911 10
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2912
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2913
        }
2914
2915 10
        return $this->associationMappings[$fieldName]['joinColumns'][0]['name'];
2916
    }
2917
2918
    /**
2919
     * Returns the single association referenced join column name (if any).
2920
     *
2921
     * @param string $fieldName
2922
     *
2923
     * @return string
2924
     *
2925
     * @throws MappingException
2926
     */
2927 10
    public function getSingleAssociationReferencedJoinColumnName($fieldName)
2928
    {
2929 10
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2930
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2931
        }
2932
2933 10
        return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName'];
2934
    }
2935
2936
    /**
2937
     * Used to retrieve a fieldname for either field or association from a given column.
2938
     *
2939
     * This method is used in foreign-key as primary-key contexts.
2940
     *
2941
     * @param string $columnName
2942
     *
2943
     * @return string
2944
     *
2945
     * @throws MappingException
2946
     */
2947 679
    public function getFieldForColumn($columnName)
2948
    {
2949 679
        if (isset($this->fieldNames[$columnName])) {
2950 657
            return $this->fieldNames[$columnName];
2951
        }
2952
2953 98
        foreach ($this->associationMappings as $assocName => $mapping) {
2954 98
            if ($this->isAssociationWithSingleJoinColumn($assocName) &&
2955 98
                $this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) {
2956
2957 98
                return $assocName;
2958
            }
2959
        }
2960
2961
        throw MappingException::noFieldNameFoundForColumn($this->name, $columnName);
2962
    }
2963
2964
    /**
2965
     * Sets the ID generator used to generate IDs for instances of this class.
2966
     *
2967
     * @param \Doctrine\ORM\Id\AbstractIdGenerator $generator
2968
     *
2969
     * @return void
2970
     */
2971 482
    public function setIdGenerator($generator)
2972
    {
2973 482
        $this->idGenerator = $generator;
2974 482
    }
2975
2976
    /**
2977
     * Sets definition.
2978
     *
2979
     * @param array $definition
2980
     *
2981
     * @return void
2982
     */
2983 13
    public function setCustomGeneratorDefinition(array $definition)
2984
    {
2985 13
        $this->customGeneratorDefinition = $definition;
2986 13
    }
2987
2988
    /**
2989
     * Sets the definition of the sequence ID generator for this class.
2990
     *
2991
     * The definition must have the following structure:
2992
     * <code>
2993
     * array(
2994
     *     'sequenceName'   => 'name',
2995
     *     'allocationSize' => 20,
2996
     *     'initialValue'   => 1
2997
     *     'quoted'         => 1
2998
     * )
2999
     * </code>
3000
     *
3001
     * @param array $definition
3002
     *
3003
     * @return void
3004
     *
3005
     * @throws MappingException
3006
     */
3007 30
    public function setSequenceGeneratorDefinition(array $definition)
3008
    {
3009 30
        if ( ! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') {
3010 1
            throw MappingException::missingSequenceName($this->name);
3011
        }
3012
3013 29
        if ($definition['sequenceName'][0] == '`') {
3014 1
            $definition['sequenceName']   = trim($definition['sequenceName'], '`');
3015 1
            $definition['quoted'] = true;
3016
        }
3017
3018 29
        if ( ! isset($definition['allocationSize']) || trim($definition['allocationSize']) === '') {
3019 2
            $definition['allocationSize'] = '1';
3020
        }
3021
3022 29
        if ( ! isset($definition['initialValue']) || trim($definition['initialValue']) === '') {
3023 2
            $definition['initialValue'] = '1';
3024
        }
3025
3026 29
        $this->sequenceGeneratorDefinition = $definition;
3027 29
    }
3028
3029
    /**
3030
     * Sets the version field mapping used for versioning. Sets the default
3031
     * value to use depending on the column type.
3032
     *
3033
     * @param array $mapping The version field mapping array.
3034
     *
3035
     * @return void
3036
     *
3037
     * @throws MappingException
3038
     */
3039 35
    public function setVersionMapping(array &$mapping)
3040
    {
3041 35
        $this->isVersioned = true;
3042 35
        $this->versionField = $mapping['fieldName'];
3043
3044 35
        if ( ! isset($mapping['default'])) {
3045 35
            if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'])) {
3046 33
                $mapping['default'] = 1;
3047 3
            } else if ($mapping['type'] == 'datetime') {
3048 2
                $mapping['default'] = 'CURRENT_TIMESTAMP';
3049
            } else {
3050 1
                throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']);
3051
            }
3052
        }
3053 34
    }
3054
3055
    /**
3056
     * Sets whether this class is to be versioned for optimistic locking.
3057
     *
3058
     * @param boolean $bool
3059
     *
3060
     * @return void
3061
     */
3062 146
    public function setVersioned($bool)
3063
    {
3064 146
        $this->isVersioned = $bool;
3065 146
    }
3066
3067
    /**
3068
     * Sets the name of the field that is to be used for versioning if this class is
3069
     * versioned for optimistic locking.
3070
     *
3071
     * @param string $versionField
3072
     *
3073
     * @return void
3074
     */
3075 146
    public function setVersionField($versionField)
3076
    {
3077 146
        $this->versionField = $versionField;
3078 146
    }
3079
3080
    /**
3081
     * Marks this class as read only, no change tracking is applied to it.
3082
     *
3083
     * @return void
3084
     */
3085 3
    public function markReadOnly()
3086
    {
3087 3
        $this->isReadOnly = true;
3088 3
    }
3089
3090
    /**
3091
     * {@inheritDoc}
3092
     */
3093
    public function getFieldNames()
3094
    {
3095
        return array_keys($this->fieldMappings);
3096
    }
3097
3098
    /**
3099
     * {@inheritDoc}
3100
     */
3101
    public function getAssociationNames()
3102
    {
3103
        return array_keys($this->associationMappings);
3104
    }
3105
3106
    /**
3107
     * {@inheritDoc}
3108
     *
3109
     * @throws InvalidArgumentException
3110
     */
3111 1
    public function getAssociationTargetClass($assocName)
3112
    {
3113 1
        if ( ! isset($this->associationMappings[$assocName])) {
3114
            throw new InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
3115
        }
3116
3117 1
        return $this->associationMappings[$assocName]['targetEntity'];
3118
    }
3119
3120
    /**
3121
     * {@inheritDoc}
3122
     */
3123 805
    public function getName()
3124
    {
3125 805
        return $this->name;
3126
    }
3127
3128
    /**
3129
     * Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
3130
     *
3131
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3132
     *
3133
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3134
     *
3135
     * @return array
3136
     */
3137
    public function getQuotedIdentifierColumnNames($platform)
3138
    {
3139
        $quotedColumnNames = [];
3140
3141
        foreach ($this->identifier as $idProperty) {
3142
            if (isset($this->fieldMappings[$idProperty])) {
3143
                $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
3144
                    ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
3145
                    : $this->fieldMappings[$idProperty]['columnName'];
3146
3147
                continue;
3148
            }
3149
3150
            // Association defined as Id field
3151
            $joinColumns            = $this->associationMappings[$idProperty]['joinColumns'];
3152
            $assocQuotedColumnNames = array_map(
3153
                function ($joinColumn) use ($platform) {
3154
                    return isset($joinColumn['quoted'])
3155
                        ? $platform->quoteIdentifier($joinColumn['name'])
3156
                        : $joinColumn['name'];
3157
                },
3158
                $joinColumns
3159
            );
3160
3161
            $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
3162
        }
3163
3164
        return $quotedColumnNames;
3165
    }
3166
3167
    /**
3168
     * Gets the (possibly quoted) column name of a mapped field for safe use  in an SQL statement.
3169
     *
3170
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3171
     *
3172
     * @param string                                    $field
3173
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3174
     *
3175
     * @return string
3176
     */
3177
    public function getQuotedColumnName($field, $platform)
3178
    {
3179
        return isset($this->fieldMappings[$field]['quoted'])
3180
            ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName'])
3181
            : $this->fieldMappings[$field]['columnName'];
3182
    }
3183
3184
    /**
3185
     * Gets the (possibly quoted) primary table name of this class for safe use in an SQL statement.
3186
     *
3187
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3188
     *
3189
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3190
     *
3191
     * @return string
3192
     */
3193
    public function getQuotedTableName($platform)
3194
    {
3195
        return isset($this->table['quoted'])
3196
            ? $platform->quoteIdentifier($this->table['name'])
3197
            : $this->table['name'];
3198
    }
3199
3200
    /**
3201
     * Gets the (possibly quoted) name of the join table.
3202
     *
3203
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3204
     *
3205
     * @param array                                     $assoc
3206
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3207
     *
3208
     * @return string
3209
     */
3210
    public function getQuotedJoinTableName(array $assoc, $platform)
3211
    {
3212
        return isset($assoc['joinTable']['quoted'])
3213
            ? $platform->quoteIdentifier($assoc['joinTable']['name'])
3214
            : $assoc['joinTable']['name'];
3215
    }
3216
3217
    /**
3218
     * {@inheritDoc}
3219
     */
3220 12
    public function isAssociationInverseSide($fieldName)
3221
    {
3222 12
        return isset($this->associationMappings[$fieldName])
3223 12
            && ! $this->associationMappings[$fieldName]['isOwningSide'];
3224
    }
3225
3226
    /**
3227
     * {@inheritDoc}
3228
     */
3229
    public function getAssociationMappedByTargetField($fieldName)
3230
    {
3231
        return $this->associationMappings[$fieldName]['mappedBy'];
3232
    }
3233
3234
    /**
3235
     * @param string $targetClass
3236
     *
3237
     * @return array
3238
     */
3239 2
    public function getAssociationsByTargetClass($targetClass)
3240
    {
3241 2
        $relations = [];
3242
3243 2
        foreach ($this->associationMappings as $mapping) {
3244 2
            if ($mapping['targetEntity'] == $targetClass) {
3245 2
                $relations[$mapping['fieldName']] = $mapping;
3246
            }
3247
        }
3248
3249 2
        return $relations;
3250
    }
3251
3252
    /**
3253
     * @param  string|null $className
3254
     *
3255
     * @return string|null null if the input value is null
3256
     */
3257 536
    public function fullyQualifiedClassName($className)
3258
    {
3259 536
        if (empty($className)) {
3260 49
            return $className;
3261
        }
3262
3263 520
        if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
3264 398
            return $this->namespace . '\\' . $className;
3265
        }
3266
3267 260
        return $className;
3268
    }
3269
3270
    /**
3271
     * @param string $name
3272
     *
3273
     * @return mixed
3274
     */
3275 2
    public function getMetadataValue($name)
3276
    {
3277
3278 2
        if (isset($this->$name)) {
3279 2
            return $this->$name;
3280
        }
3281
3282
        return null;
3283
    }
3284
3285
    /**
3286
     * Map Embedded Class
3287
     *
3288
     * @param array $mapping
3289
     *
3290
     * @throws MappingException
3291
     * @return void
3292
     */
3293 31
    public function mapEmbedded(array $mapping)
3294
    {
3295 31
        $this->assertFieldNotMapped($mapping['fieldName']);
3296
3297 31
        $this->embeddedClasses[$mapping['fieldName']] = [
3298 31
            'class' => $this->fullyQualifiedClassName($mapping['class']),
3299 31
            'columnPrefix' => $mapping['columnPrefix'],
3300 31
            'declaredField' => $mapping['declaredField'] ?? null,
3301 31
            'originalField' => $mapping['originalField'] ?? null,
3302
        ];
3303 31
    }
3304
3305
    /**
3306
     * Inline the embeddable class
3307
     *
3308
     * @param string            $property
3309
     * @param ClassMetadataInfo $embeddable
3310
     */
3311 12
    public function inlineEmbeddable($property, ClassMetadataInfo $embeddable)
3312
    {
3313 12
        foreach ($embeddable->fieldMappings as $fieldMapping) {
3314 12
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->name;
3315 12
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
3316 4
                ? $property . '.' . $fieldMapping['declaredField']
3317 12
                : $property;
3318 12
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldMapping['fieldName'];
3319 12
            $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName'];
3320
3321 12
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
3322 2
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
3323 11
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
3324 8
                $fieldMapping['columnName'] = $this->namingStrategy
3325 8
                    ->embeddedFieldToColumnName(
3326 8
                        $property,
3327 8
                        $fieldMapping['columnName'],
3328 8
                        $this->reflClass->name,
3329 8
                        $embeddable->reflClass->name
3330
                    );
3331
            }
3332
3333 12
            $this->mapField($fieldMapping);
3334
        }
3335 12
    }
3336
3337
    /**
3338
     * @param string $fieldName
3339
     * @throws MappingException
3340
     */
3341 657
    private function assertFieldNotMapped($fieldName)
3342
    {
3343 657
        if (isset($this->fieldMappings[$fieldName]) ||
3344 657
            isset($this->associationMappings[$fieldName]) ||
3345 657
            isset($this->embeddedClasses[$fieldName])) {
3346
3347 2
            throw MappingException::duplicateFieldMapping($this->name, $fieldName);
3348
        }
3349 657
    }
3350
3351
    /**
3352
     * Gets the sequence name based on class metadata.
3353
     *
3354
     * @param AbstractPlatform $platform
3355
     * @return string
3356
     *
3357
     * @todo Sequence names should be computed in DBAL depending on the platform
3358
     */
3359 3
    public function getSequenceName(AbstractPlatform $platform)
3360
    {
3361 3
        $sequencePrefix = $this->getSequencePrefix($platform);
3362 3
        $columnName     = $this->getSingleIdentifierColumnName();
3363 3
        $sequenceName   = $sequencePrefix . '_' . $columnName . '_seq';
3364
3365 3
        return $sequenceName;
3366
    }
3367
3368
    /**
3369
     * Gets the sequence name prefix based on class metadata.
3370
     *
3371
     * @param AbstractPlatform $platform
3372
     * @return string
3373
     *
3374
     * @todo Sequence names should be computed in DBAL depending on the platform
3375
     */
3376 3
    public function getSequencePrefix(AbstractPlatform $platform)
3377
    {
3378 3
        $tableName      = $this->getTableName();
3379 3
        $sequencePrefix = $tableName;
3380
3381
        // Prepend the schema name to the table name if there is one
3382 3
        if ($schemaName = $this->getSchemaName()) {
3383 3
            $sequencePrefix = $schemaName . '.' . $tableName;
3384
3385 3
            if ( ! $platform->supportsSchemas() && $platform->canEmulateSchemas()) {
3386 3
                $sequencePrefix = $schemaName . '__' . $tableName;
3387
            }
3388
        }
3389
3390 3
        return $sequencePrefix;
3391
    }
3392
3393
    /**
3394
     * @param array $mapping
3395
     */
3396 242
    private function assertMappingOrderBy(array $mapping)
3397
    {
3398 242
        if (isset($mapping['orderBy']) && !is_array($mapping['orderBy'])) {
3399
            throw new InvalidArgumentException("'orderBy' is expected to be an array, not " . gettype($mapping['orderBy']));
3400
        }
3401 242
    }
3402
}
3403