Failed Conditions
Push — master ( 6744b4...2b8acb )
by Marco
60:45 queued 60:36
created

lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
935 15
                        $this->embeddedClasses[$embeddedClass['declaredField']]['class'],
936 15
                        $embeddedClass['originalField']
937
                    ),
938 15
                    $this->embeddedClasses[$embeddedClass['declaredField']]['class']
939
                );
940
941 15
                continue;
942
            }
943
944 23
            $fieldRefl = $reflService->getAccessibleProperty(
945 23
                isset($embeddedClass['declared']) ? $embeddedClass['declared'] : $this->name,
946 23
                $property
947
            );
948
949 23
            $parentReflFields[$property] = $fieldRefl;
950 23
            $this->reflFields[$property] = $fieldRefl;
951
        }
952
953 2065
        foreach ($this->fieldMappings as $field => $mapping) {
954 2060
            if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
955 22
                $this->reflFields[$field] = new ReflectionEmbeddedProperty(
956 22
                    $parentReflFields[$mapping['declaredField']],
957 22
                    $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
0 ignored issues
show
It seems like $reflService->getAccessi...pping['originalField']) can be null; however, __construct() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
958 22
                    $mapping['originalClass']
959
                );
960 22
                continue;
961
            }
962
963 2060
            $this->reflFields[$field] = isset($mapping['declared'])
964 523
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
965 2060
                : $reflService->getAccessibleProperty($this->name, $field);
966
        }
967
968 2065
        foreach ($this->associationMappings as $field => $mapping) {
969 1725
            $this->reflFields[$field] = isset($mapping['declared'])
970 425
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
971 1725
                : $reflService->getAccessibleProperty($this->name, $field);
972
        }
973 2065
    }
974
975
    /**
976
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
977
     * metadata of the class with the given name.
978
     *
979
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService The reflection service.
980
     *
981
     * @return void
982
     */
983 657
    public function initializeReflection($reflService)
984
    {
985 657
        $this->reflClass = $reflService->getClass($this->name);
986 657
        $this->namespace = $reflService->getClassNamespace($this->name);
987
988 657
        if ($this->reflClass) {
989 650
            $this->name = $this->rootEntityName = $this->reflClass->getName();
990
        }
991
992 657
        $this->table['name'] = $this->namingStrategy->classToTableName($this->name);
993 657
    }
994
995
    /**
996
     * Validates Identifier.
997
     *
998
     * @return void
999
     *
1000
     * @throws MappingException
1001
     */
1002 439
    public function validateIdentifier()
1003
    {
1004 439
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
1005 63
            return;
1006
        }
1007
1008
        // Verify & complete identifier mapping
1009 437
        if ( ! $this->identifier) {
1010 6
            throw MappingException::identifierRequired($this->name);
1011
        }
1012
1013 431
        if ($this->usesIdGenerator() && $this->isIdentifierComposite) {
1014
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name);
1015
        }
1016 431
    }
1017
1018
    /**
1019
     * Validates association targets actually exist.
1020
     *
1021
     * @return void
1022
     *
1023
     * @throws MappingException
1024
     */
1025 440
    public function validateAssociations()
1026
    {
1027 440
        foreach ($this->associationMappings as $mapping) {
1028 284
            if ( ! ClassLoader::classExists($mapping['targetEntity']) ) {
1029 284
                throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']);
1030
            }
1031
        }
1032 439
    }
1033
1034
    /**
1035
     * Validates lifecycle callbacks.
1036
     *
1037
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService
1038
     *
1039
     * @return void
1040
     *
1041
     * @throws MappingException
1042
     */
1043 440
    public function validateLifecycleCallbacks($reflService)
1044
    {
1045 440
        foreach ($this->lifecycleCallbacks as $callbacks) {
1046 13
            foreach ($callbacks as $callbackFuncName) {
1047 13
                if ( ! $reflService->hasPublicMethod($this->name, $callbackFuncName)) {
1048 13
                    throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName);
1049
                }
1050
            }
1051
        }
1052 439
    }
1053
1054
    /**
1055
     * {@inheritDoc}
1056
     */
1057 571
    public function getReflectionClass()
1058
    {
1059 571
        return $this->reflClass;
1060
    }
1061
1062
    /**
1063
     * @param array $cache
1064
     *
1065
     * @return void
1066
     */
1067 23
    public function enableCache(array $cache)
1068
    {
1069 23
        if ( ! isset($cache['usage'])) {
1070
            $cache['usage'] = self::CACHE_USAGE_READ_ONLY;
1071
        }
1072
1073 23 View Code Duplication
        if ( ! isset($cache['region'])) {
1074 23
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName));
1075
        }
1076
1077 23
        $this->cache = $cache;
1078 23
    }
1079
1080
    /**
1081
     * @param string $fieldName
1082
     * @param array  $cache
1083
     *
1084
     * @return void
1085
     */
1086 2
    public function enableAssociationCache($fieldName, array $cache)
1087
    {
1088 2
        $this->associationMappings[$fieldName]['cache'] = $this->getAssociationCacheDefaults($fieldName, $cache);
1089 2
    }
1090
1091
    /**
1092
     * @param string $fieldName
1093
     * @param array  $cache
1094
     *
1095
     * @return array
1096
     */
1097 18
    public function getAssociationCacheDefaults($fieldName, array $cache)
1098
    {
1099 18
        if ( ! isset($cache['usage'])) {
1100 1
            $cache['usage'] = isset($this->cache['usage'])
1101 1
                ? $this->cache['usage']
1102
                : self::CACHE_USAGE_READ_ONLY;
1103
        }
1104
1105 18 View Code Duplication
        if ( ! isset($cache['region'])) {
1106 18
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
1107
        }
1108
1109 18
        return $cache;
1110
    }
1111
1112
    /**
1113
     * Sets the change tracking policy used by this class.
1114
     *
1115
     * @param integer $policy
1116
     *
1117
     * @return void
1118
     */
1119 155
    public function setChangeTrackingPolicy($policy)
1120
    {
1121 155
        $this->changeTrackingPolicy = $policy;
1122 155
    }
1123
1124
    /**
1125
     * Whether the change tracking policy of this class is "deferred explicit".
1126
     *
1127
     * @return boolean
1128
     */
1129 272
    public function isChangeTrackingDeferredExplicit()
1130
    {
1131 272
        return self::CHANGETRACKING_DEFERRED_EXPLICIT === $this->changeTrackingPolicy;
1132
    }
1133
1134
    /**
1135
     * Whether the change tracking policy of this class is "deferred implicit".
1136
     *
1137
     * @return boolean
1138
     */
1139 468
    public function isChangeTrackingDeferredImplicit()
1140
    {
1141 468
        return self::CHANGETRACKING_DEFERRED_IMPLICIT === $this->changeTrackingPolicy;
1142
    }
1143
1144
    /**
1145
     * Whether the change tracking policy of this class is "notify".
1146
     *
1147
     * @return boolean
1148
     */
1149 301
    public function isChangeTrackingNotify()
1150
    {
1151 301
        return self::CHANGETRACKING_NOTIFY === $this->changeTrackingPolicy;
1152
    }
1153
1154
    /**
1155
     * Checks whether a field is part of the identifier/primary key field(s).
1156
     *
1157
     * @param string $fieldName The field name.
1158
     *
1159
     * @return boolean TRUE if the field is part of the table identifier/primary key field(s),
1160
     *                 FALSE otherwise.
1161
     */
1162 1101
    public function isIdentifier($fieldName)
1163
    {
1164 1101
        if ( ! $this->identifier) {
1165 1
            return false;
1166
        }
1167
1168 1100
        if ( ! $this->isIdentifierComposite) {
1169 1095
            return $fieldName === $this->identifier[0];
1170
        }
1171
1172 94
        return in_array($fieldName, $this->identifier, true);
1173
    }
1174
1175
    /**
1176
     * Checks if the field is unique.
1177
     *
1178
     * @param string $fieldName The field name.
1179
     *
1180
     * @return boolean TRUE if the field is unique, FALSE otherwise.
1181
     */
1182
    public function isUniqueField($fieldName)
1183
    {
1184
        $mapping = $this->getFieldMapping($fieldName);
1185
1186
        return false !== $mapping && isset($mapping['unique']) && $mapping['unique'];
1187
    }
1188
1189
    /**
1190
     * Checks if the field is not null.
1191
     *
1192
     * @param string $fieldName The field name.
1193
     *
1194
     * @return boolean TRUE if the field is not null, FALSE otherwise.
1195
     */
1196 1
    public function isNullable($fieldName)
1197
    {
1198 1
        $mapping = $this->getFieldMapping($fieldName);
1199
1200 1
        return false !== $mapping && isset($mapping['nullable']) && $mapping['nullable'];
1201
    }
1202
1203
    /**
1204
     * Gets a column name for a field name.
1205
     * If the column name for the field cannot be found, the given field name
1206
     * is returned.
1207
     *
1208
     * @param string $fieldName The field name.
1209
     *
1210
     * @return string The column name.
1211
     */
1212 16
    public function getColumnName($fieldName)
1213
    {
1214 16
        return isset($this->columnNames[$fieldName])
1215 16
            ? $this->columnNames[$fieldName]
1216 16
            : $fieldName;
1217
    }
1218
1219
    /**
1220
     * Gets the mapping of a (regular) field that holds some data but not a
1221
     * reference to another object.
1222
     *
1223
     * @param string $fieldName The field name.
1224
     *
1225
     * @return array The field mapping.
1226
     *
1227
     * @throws MappingException
1228
     */
1229 206 View Code Duplication
    public function getFieldMapping($fieldName)
1230
    {
1231 206
        if ( ! isset($this->fieldMappings[$fieldName])) {
1232 1
            throw MappingException::mappingNotFound($this->name, $fieldName);
1233
        }
1234
1235 205
        return $this->fieldMappings[$fieldName];
1236
    }
1237
1238
    /**
1239
     * Gets the mapping of an association.
1240
     *
1241
     * @see ClassMetadataInfo::$associationMappings
1242
     *
1243
     * @param string $fieldName The field name that represents the association in
1244
     *                          the object model.
1245
     *
1246
     * @return array The mapping.
1247
     *
1248
     * @throws MappingException
1249
     */
1250 493 View Code Duplication
    public function getAssociationMapping($fieldName)
1251
    {
1252 493
        if ( ! isset($this->associationMappings[$fieldName])) {
1253
            throw MappingException::mappingNotFound($this->name, $fieldName);
1254
        }
1255
1256 493
        return $this->associationMappings[$fieldName];
1257
    }
1258
1259
    /**
1260
     * Gets all association mappings of the class.
1261
     *
1262
     * @return array
1263
     */
1264
    public function getAssociationMappings()
1265
    {
1266
        return $this->associationMappings;
1267
    }
1268
1269
    /**
1270
     * Gets the field name for a column name.
1271
     * If no field name can be found the column name is returned.
1272
     *
1273
     * @param string $columnName The column name.
1274
     *
1275
     * @return string The column alias.
1276
     */
1277 246
    public function getFieldName($columnName)
1278
    {
1279 246
        return isset($this->fieldNames[$columnName])
1280 246
            ? $this->fieldNames[$columnName]
1281 246
            : $columnName;
1282
    }
1283
1284
    /**
1285
     * Gets the named query.
1286
     *
1287
     * @see ClassMetadataInfo::$namedQueries
1288
     *
1289
     * @param string $queryName The query name.
1290
     *
1291
     * @return string
1292
     *
1293
     * @throws MappingException
1294
     */
1295 4 View Code Duplication
    public function getNamedQuery($queryName)
1296
    {
1297 4
        if ( ! isset($this->namedQueries[$queryName])) {
1298 1
            throw MappingException::queryNotFound($this->name, $queryName);
1299
        }
1300
1301 3
        return $this->namedQueries[$queryName]['dql'];
1302
    }
1303
1304
    /**
1305
     * Gets all named queries of the class.
1306
     *
1307
     * @return array
1308
     */
1309 7
    public function getNamedQueries()
1310
    {
1311 7
        return $this->namedQueries;
1312
    }
1313
1314
    /**
1315
     * Gets the named native query.
1316
     *
1317
     * @see ClassMetadataInfo::$namedNativeQueries
1318
     *
1319
     * @param string $queryName The query name.
1320
     *
1321
     * @return array
1322
     *
1323
     * @throws MappingException
1324
     */
1325 17 View Code Duplication
    public function getNamedNativeQuery($queryName)
1326
    {
1327 17
        if ( ! isset($this->namedNativeQueries[$queryName])) {
1328
            throw MappingException::queryNotFound($this->name, $queryName);
1329
        }
1330
1331 17
        return $this->namedNativeQueries[$queryName];
1332
    }
1333
1334
    /**
1335
     * Gets all named native queries of the class.
1336
     *
1337
     * @return array
1338
     */
1339 2
    public function getNamedNativeQueries()
1340
    {
1341 2
        return $this->namedNativeQueries;
1342
    }
1343
1344
    /**
1345
     * Gets the result set mapping.
1346
     *
1347
     * @see ClassMetadataInfo::$sqlResultSetMappings
1348
     *
1349
     * @param string $name The result set mapping name.
1350
     *
1351
     * @return array
1352
     *
1353
     * @throws MappingException
1354
     */
1355 21
    public function getSqlResultSetMapping($name)
1356
    {
1357 21
        if ( ! isset($this->sqlResultSetMappings[$name])) {
1358
            throw MappingException::resultMappingNotFound($this->name, $name);
1359
        }
1360
1361 21
        return $this->sqlResultSetMappings[$name];
1362
    }
1363
1364
    /**
1365
     * Gets all sql result set mappings of the class.
1366
     *
1367
     * @return array
1368
     */
1369 8
    public function getSqlResultSetMappings()
1370
    {
1371 8
        return $this->sqlResultSetMappings;
1372
    }
1373
1374
    /**
1375
     * Validates & completes the given field mapping.
1376
     *
1377
     * @param array $mapping The field mapping to validate & complete.
1378
     *
1379
     * @return void
1380
     *
1381
     * @throws MappingException
1382
     */
1383 575
    protected function _validateAndCompleteFieldMapping(array &$mapping)
1384
    {
1385
        // Check mandatory fields
1386 575 View Code Duplication
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
1387 1
            throw MappingException::missingFieldName($this->name);
1388
        }
1389
1390 574
        if ( ! isset($mapping['type'])) {
1391
            // Default to string
1392 64
            $mapping['type'] = 'string';
1393
        }
1394
1395
        // Complete fieldName and columnName mapping
1396 574
        if ( ! isset($mapping['columnName'])) {
1397 477
            $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name);
1398
        }
1399
1400 574
        if ('`' === $mapping['columnName'][0]) {
1401 11
            $mapping['columnName']  = trim($mapping['columnName'], '`');
1402 11
            $mapping['quoted']      = true;
1403
        }
1404
1405 574
        $this->columnNames[$mapping['fieldName']] = $mapping['columnName'];
1406
1407 574
        if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorColumn && $this->discriminatorColumn['name'] === $mapping['columnName'])) {
1408 2
            throw MappingException::duplicateColumnName($this->name, $mapping['columnName']);
1409
        }
1410
1411 573
        $this->fieldNames[$mapping['columnName']] = $mapping['fieldName'];
1412
1413
        // Complete id mapping
1414 573
        if (isset($mapping['id']) && true === $mapping['id']) {
1415 533
            if ($this->versionField == $mapping['fieldName']) {
1416
                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
1417
            }
1418
1419 533
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1420 533
                $this->identifier[] = $mapping['fieldName'];
1421
            }
1422
1423
            // Check for composite key
1424 533 View Code Duplication
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1425 22
                $this->isIdentifierComposite = true;
1426
            }
1427
        }
1428
1429 573
        if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
1430 5 View Code Duplication
            if (isset($mapping['id']) && true === $mapping['id']) {
1431
                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
1432
            }
1433
1434 5
            $mapping['requireSQLConversion'] = true;
1435
        }
1436 573
    }
1437
1438
    /**
1439
     * Validates & completes the basic mapping information that is common to all
1440
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
1441
     *
1442
     * @param array $mapping The mapping.
1443
     *
1444
     * @return array The updated mapping.
1445
     *
1446
     * @throws MappingException If something is wrong with the mapping.
1447
     */
1448 373
    protected function _validateAndCompleteAssociationMapping(array $mapping)
1449
    {
1450 373
        if ( ! isset($mapping['mappedBy'])) {
1451 359
            $mapping['mappedBy'] = null;
1452
        }
1453
1454 373
        if ( ! isset($mapping['inversedBy'])) {
1455 343
            $mapping['inversedBy'] = null;
1456
        }
1457
1458 373
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
1459
1460 373
        if (empty($mapping['indexBy'])) {
1461 370
            unset($mapping['indexBy']);
1462
        }
1463
1464
        // If targetEntity is unqualified, assume it is in the same namespace as
1465
        // the sourceEntity.
1466 373
        $mapping['sourceEntity'] = $this->name;
1467
1468 373
        if (isset($mapping['targetEntity'])) {
1469 373
            $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
1470 373
            $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
1471
        }
1472
1473 373 View Code Duplication
        if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1474 1
            throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
1475
        }
1476
1477
        // Complete id mapping
1478 372
        if (isset($mapping['id']) && true === $mapping['id']) {
1479 54
            if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1480 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
1481
            }
1482
1483 53
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1484 53
                if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) {
1485
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
1486
                        $mapping['targetEntity'], $this->name, $mapping['fieldName']
1487
                    );
1488
                }
1489
1490 53
                $this->identifier[] = $mapping['fieldName'];
1491 53
                $this->containsForeignIdentifier = true;
1492
            }
1493
1494
            // Check for composite key
1495 53 View Code Duplication
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1496 25
                $this->isIdentifierComposite = true;
1497
            }
1498
1499 53
            if ($this->cache && !isset($mapping['cache'])) {
1500 3
                throw CacheException::nonCacheableEntityAssociation($this->name, $mapping['fieldName']);
1501
            }
1502
        }
1503
1504
        // Mandatory attributes for both sides
1505
        // Mandatory: fieldName, targetEntity
1506 368 View Code Duplication
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
1507
            throw MappingException::missingFieldName($this->name);
1508
        }
1509
1510 368
        if ( ! isset($mapping['targetEntity'])) {
1511
            throw MappingException::missingTargetEntity($mapping['fieldName']);
1512
        }
1513
1514
        // Mandatory and optional attributes for either side
1515 368
        if ( ! $mapping['mappedBy']) {
1516 354
            if (isset($mapping['joinTable']) && $mapping['joinTable']) {
1517 136
                if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
1518 4
                    $mapping['joinTable']['name']   = trim($mapping['joinTable']['name'], '`');
1519 354
                    $mapping['joinTable']['quoted'] = true;
1520
                }
1521
            }
1522
        } else {
1523 197
            $mapping['isOwningSide'] = false;
1524
        }
1525
1526 368 View Code Duplication
        if (isset($mapping['id']) && true === $mapping['id'] && $mapping['type'] & self::TO_MANY) {
1527 3
            throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']);
1528
        }
1529
1530
        // Fetch mode. Default fetch mode to LAZY, if not set.
1531 365
        if ( ! isset($mapping['fetch'])) {
1532 104
            $mapping['fetch'] = self::FETCH_LAZY;
1533
        }
1534
1535
        // Cascades
1536 365
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
1537
1538 365
        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1539 365
        if (in_array('all', $cascades)) {
1540 41
            $cascades = $allCascades;
1541 358
        } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
1542 1
            throw MappingException::invalidCascadeOption(
1543 1
                array_diff($cascades, $allCascades),
1544 1
                $this->name,
1545 1
                $mapping['fieldName']
1546
            );
1547
        }
1548
1549 364
        $mapping['cascade'] = $cascades;
1550 364
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1551 364
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1552 364
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1553 364
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1554 364
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1555
1556 364
        return $mapping;
1557
    }
1558
1559
    /**
1560
     * Validates & completes a one-to-one association mapping.
1561
     *
1562
     * @param array $mapping The mapping to validate & complete.
1563
     *
1564
     * @return array The validated & completed mapping.
1565
     *
1566
     * @throws RuntimeException
1567
     * @throws MappingException
1568
     */
1569 314
    protected function _validateAndCompleteOneToOneMapping(array $mapping)
1570
    {
1571 314
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1572
1573 308
        if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
1574 223
            $mapping['isOwningSide'] = true;
1575
        }
1576
1577 308
        if ($mapping['isOwningSide']) {
1578 295
            if (empty($mapping['joinColumns'])) {
1579
                // Apply default join column
1580 93
                $mapping['joinColumns'] = [
1581
                    [
1582 93
                        'name' => $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name),
1583 93
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName()
1584
                    ]
1585
                ];
1586
            }
1587
1588 295
            $uniqueConstraintColumns = [];
1589
1590 295
            foreach ($mapping['joinColumns'] as &$joinColumn) {
1591 295
                if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
1592 161
                    if (count($mapping['joinColumns']) === 1) {
1593 159
                        if (empty($mapping['id'])) {
1594 159
                            $joinColumn['unique'] = true;
1595
                        }
1596
                    } else {
1597 2
                        $uniqueConstraintColumns[] = $joinColumn['name'];
1598
                    }
1599
                }
1600
1601 295
                if (empty($joinColumn['name'])) {
1602 33
                    $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name);
1603
                }
1604
1605 295
                if (empty($joinColumn['referencedColumnName'])) {
1606 5
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1607
                }
1608
1609 295
                if ($joinColumn['name'][0] === '`') {
1610 7
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1611 7
                    $joinColumn['quoted'] = true;
1612
                }
1613
1614 295
                if ($joinColumn['referencedColumnName'][0] === '`') {
1615 4
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1616 4
                    $joinColumn['quoted']               = true;
1617
                }
1618
1619 295
                $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1620 295
                $mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName'])
1621
                    ? $joinColumn['fieldName']
1622 295
                    : $joinColumn['name'];
1623
            }
1624
1625 295
            if ($uniqueConstraintColumns) {
1626 2
                if ( ! $this->table) {
1627
                    throw new RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.");
1628
                }
1629
1630 2
                $this->table['uniqueConstraints'][$mapping['fieldName'] . "_uniq"] = [
1631 2
                    'columns' => $uniqueConstraintColumns
1632
                ];
1633
            }
1634
1635 295
            $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
1636
        }
1637
1638 308
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1639 308
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1640
1641 308
        if ($mapping['orphanRemoval']) {
1642 22
            unset($mapping['unique']);
1643
        }
1644
1645 308 View Code Duplication
        if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
1646 2
            throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
1647
        }
1648
1649 306
        return $mapping;
1650
    }
1651
1652
    /**
1653
     * Validates & completes a one-to-many association mapping.
1654
     *
1655
     * @param array $mapping The mapping to validate and complete.
1656
     *
1657
     * @return array The validated and completed mapping.
1658
     *
1659
     * @throws MappingException
1660
     * @throws InvalidArgumentException
1661
     */
1662 141
    protected function _validateAndCompleteOneToManyMapping(array $mapping)
1663
    {
1664 141
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1665
1666
        // OneToMany-side MUST be inverse (must have mappedBy)
1667 140
        if ( ! isset($mapping['mappedBy'])) {
1668
            throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
1669
        }
1670
1671 140
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1672 140
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1673
1674 140
        $this->assertMappingOrderBy($mapping);
1675
1676 140
        return $mapping;
1677
    }
1678
1679
    /**
1680
     * Validates & completes a many-to-many association mapping.
1681
     *
1682
     * @param array $mapping The mapping to validate & complete.
1683
     *
1684
     * @return array The validated & completed mapping.
1685
     *
1686
     * @throws \InvalidArgumentException
1687
     */
1688 166
    protected function _validateAndCompleteManyToManyMapping(array $mapping)
1689
    {
1690 166
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1691
1692 164
        if ($mapping['isOwningSide']) {
1693
            // owning side MUST have a join table
1694 146
            if ( ! isset($mapping['joinTable']['name'])) {
1695 28
                $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
1696
            }
1697
1698 146
            $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
1699 146
                && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
1700
1701 146 View Code Duplication
            if ( ! isset($mapping['joinTable']['joinColumns'])) {
1702 27
                $mapping['joinTable']['joinColumns'] = [
1703
                    [
1704 27
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
1705 27
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1706 27
                        'onDelete' => 'CASCADE'
1707
                    ]
1708
                ];
1709
            }
1710
1711 146 View Code Duplication
            if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
1712 28
                $mapping['joinTable']['inverseJoinColumns'] = [
1713
                    [
1714 28
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
1715 28
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1716 28
                        'onDelete' => 'CASCADE'
1717
                    ]
1718
                ];
1719
            }
1720
1721 146
            $mapping['joinTableColumns'] = [];
1722
1723 146 View Code Duplication
            foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
1724 146
                if (empty($joinColumn['name'])) {
1725 2
                    $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
1726
                }
1727
1728 146
                if (empty($joinColumn['referencedColumnName'])) {
1729 6
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1730
                }
1731
1732 146
                if ($joinColumn['name'][0] === '`') {
1733 3
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1734 3
                    $joinColumn['quoted'] = true;
1735
                }
1736
1737 146
                if ($joinColumn['referencedColumnName'][0] === '`') {
1738 3
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1739 3
                    $joinColumn['quoted']               = true;
1740
                }
1741
1742 146
                if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') {
1743 37
                    $mapping['isOnDeleteCascade'] = true;
1744
                }
1745
1746 146
                $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1747 146
                $mapping['joinTableColumns'][] = $joinColumn['name'];
1748
            }
1749
1750 146 View Code Duplication
            foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) {
1751 146
                if (empty($inverseJoinColumn['name'])) {
1752 2
                    $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']);
1753
                }
1754
1755 146
                if (empty($inverseJoinColumn['referencedColumnName'])) {
1756 6
                    $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1757
                }
1758
1759 146
                if ($inverseJoinColumn['name'][0] === '`') {
1760 3
                    $inverseJoinColumn['name']   = trim($inverseJoinColumn['name'], '`');
1761 3
                    $inverseJoinColumn['quoted'] = true;
1762
                }
1763
1764 146
                if ($inverseJoinColumn['referencedColumnName'][0] === '`') {
1765 3
                    $inverseJoinColumn['referencedColumnName']  = trim($inverseJoinColumn['referencedColumnName'], '`');
1766 3
                    $inverseJoinColumn['quoted']                = true;
1767
                }
1768
1769 146
                if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
1770 33
                    $mapping['isOnDeleteCascade'] = true;
1771
                }
1772
1773 146
                $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
1774 146
                $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
1775
            }
1776
        }
1777
1778 164
        $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1779
1780 164
        $this->assertMappingOrderBy($mapping);
1781
1782 164
        return $mapping;
1783
    }
1784
1785
    /**
1786
     * {@inheritDoc}
1787
     */
1788 624
    public function getIdentifierFieldNames()
1789
    {
1790 624
        return $this->identifier;
1791
    }
1792
1793
    /**
1794
     * Gets the name of the single id field. Note that this only works on
1795
     * entity classes that have a single-field pk.
1796
     *
1797
     * @return string
1798
     *
1799
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1800
     */
1801 1163
    public function getSingleIdentifierFieldName()
1802
    {
1803 1163
        if ($this->isIdentifierComposite) {
1804 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
1805
        }
1806
1807 1162
        if ( ! isset($this->identifier[0])) {
1808 1
            throw MappingException::noIdDefined($this->name);
1809
        }
1810
1811 1161
        return $this->identifier[0];
1812
    }
1813
1814
    /**
1815
     * Gets the column name of the single id column. Note that this only works on
1816
     * entity classes that have a single-field pk.
1817
     *
1818
     * @return string
1819
     *
1820
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1821
     */
1822 3
    public function getSingleIdentifierColumnName()
1823
    {
1824 3
        return $this->getColumnName($this->getSingleIdentifierFieldName());
1825
    }
1826
1827
    /**
1828
     * INTERNAL:
1829
     * Sets the mapped identifier/primary key fields of this class.
1830
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
1831
     *
1832
     * @param array $identifier
1833
     *
1834
     * @return void
1835
     */
1836 138
    public function setIdentifier(array $identifier)
1837
    {
1838 138
        $this->identifier = $identifier;
1839 138
        $this->isIdentifierComposite = (count($this->identifier) > 1);
1840 138
    }
1841
1842
    /**
1843
     * {@inheritDoc}
1844
     */
1845 65
    public function getIdentifier()
1846
    {
1847 65
        return $this->identifier;
1848
    }
1849
1850
    /**
1851
     * {@inheritDoc}
1852
     */
1853 308
    public function hasField($fieldName)
1854
    {
1855 308
        return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
1856
    }
1857
1858
    /**
1859
     * Gets an array containing all the column names.
1860
     *
1861
     * @param array|null $fieldNames
1862
     *
1863
     * @return array
1864
     */
1865 44
    public function getColumnNames(array $fieldNames = null)
1866
    {
1867 44
        if (null === $fieldNames) {
1868 43
            return array_keys($this->fieldNames);
1869
        }
1870
1871 1
        return array_values(array_map([$this, 'getColumnName'], $fieldNames));
1872
    }
1873
1874
    /**
1875
     * Returns an array with all the identifier column names.
1876
     *
1877
     * @return array
1878
     */
1879 326
    public function getIdentifierColumnNames()
1880
    {
1881 326
        $columnNames = [];
1882
1883 326
        foreach ($this->identifier as $idProperty) {
1884 326
            if (isset($this->fieldMappings[$idProperty])) {
1885 322
                $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
1886
1887 322
                continue;
1888
            }
1889
1890
            // Association defined as Id field
1891 22
            $joinColumns      = $this->associationMappings[$idProperty]['joinColumns'];
1892
            $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
1893
1894 22
            $columnNames = array_merge($columnNames, $assocColumnNames);
1895
        }
1896
1897 326
        return $columnNames;
1898
    }
1899
1900
    /**
1901
     * Sets the type of Id generator to use for the mapped class.
1902
     *
1903
     * @param int $generatorType
1904
     *
1905
     * @return void
1906
     */
1907 488
    public function setIdGeneratorType($generatorType)
1908
    {
1909 488
        $this->generatorType = $generatorType;
1910 488
    }
1911
1912
    /**
1913
     * Checks whether the mapped class uses an Id generator.
1914
     *
1915
     * @return boolean TRUE if the mapped class uses an Id generator, FALSE otherwise.
1916
     */
1917 431
    public function usesIdGenerator()
1918
    {
1919 431
        return $this->generatorType != self::GENERATOR_TYPE_NONE;
1920
    }
1921
1922
    /**
1923
     * @return boolean
1924
     */
1925 1381
    public function isInheritanceTypeNone()
1926
    {
1927 1381
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1928
    }
1929
1930
    /**
1931
     * Checks whether the mapped class uses the JOINED inheritance mapping strategy.
1932
     *
1933
     * @return boolean TRUE if the class participates in a JOINED inheritance mapping,
1934
     *                 FALSE otherwise.
1935
     */
1936 1072
    public function isInheritanceTypeJoined()
1937
    {
1938 1072
        return $this->inheritanceType == self::INHERITANCE_TYPE_JOINED;
1939
    }
1940
1941
    /**
1942
     * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy.
1943
     *
1944
     * @return boolean TRUE if the class participates in a SINGLE_TABLE inheritance mapping,
1945
     *                 FALSE otherwise.
1946
     */
1947 1266
    public function isInheritanceTypeSingleTable()
1948
    {
1949 1266
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE;
1950
    }
1951
1952
    /**
1953
     * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy.
1954
     *
1955
     * @return boolean TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping,
1956
     *                 FALSE otherwise.
1957
     */
1958 271
    public function isInheritanceTypeTablePerClass()
1959
    {
1960 271
        return $this->inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
1961
    }
1962
1963
    /**
1964
     * Checks whether the class uses an identity column for the Id generation.
1965
     *
1966
     * @return boolean TRUE if the class uses the IDENTITY generator, FALSE otherwise.
1967
     */
1968 1090
    public function isIdGeneratorIdentity()
1969
    {
1970 1090
        return $this->generatorType == self::GENERATOR_TYPE_IDENTITY;
1971
    }
1972
1973
    /**
1974
     * Checks whether the class uses a sequence for id generation.
1975
     *
1976
     * @return boolean TRUE if the class uses the SEQUENCE generator, FALSE otherwise.
1977
     */
1978 335
    public function isIdGeneratorSequence()
1979
    {
1980 335
        return $this->generatorType == self::GENERATOR_TYPE_SEQUENCE;
1981
    }
1982
1983
    /**
1984
     * Checks whether the class uses a table for id generation.
1985
     *
1986
     * @return boolean TRUE if the class uses the TABLE generator, FALSE otherwise.
1987
     */
1988 92
    public function isIdGeneratorTable()
1989
    {
1990 92
        return $this->generatorType == self::GENERATOR_TYPE_TABLE;
1991
    }
1992
1993
    /**
1994
     * Checks whether the class has a natural identifier/pk (which means it does
1995
     * not use any Id generator.
1996
     *
1997
     * @return boolean
1998
     */
1999 74
    public function isIdentifierNatural()
2000
    {
2001 74
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
2002
    }
2003
2004
    /**
2005
     * Checks whether the class use a UUID for id generation.
2006
     *
2007
     * @return boolean
2008
     */
2009
    public function isIdentifierUuid()
2010
    {
2011
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
2012
    }
2013
2014
    /**
2015
     * Gets the type of a field.
2016
     *
2017
     * @param string $fieldName
2018
     *
2019
     * @return \Doctrine\DBAL\Types\Type|string|null
2020
     *
2021
     * @todo 3.0 Remove this. PersisterHelper should fix it somehow
2022
     */
2023 940
    public function getTypeOfField($fieldName)
2024
    {
2025 940
        return isset($this->fieldMappings[$fieldName])
2026 940
            ? $this->fieldMappings[$fieldName]['type']
2027 940
            : null;
2028
    }
2029
2030
    /**
2031
     * Gets the type of a column.
2032
     *
2033
     * @param string $columnName
2034
     *
2035
     * @return \Doctrine\DBAL\Types\Type|string|null
2036
     *
2037
     * @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
2038
     *             that is derived by a referenced field on a different entity.
2039
     */
2040
    public function getTypeOfColumn($columnName)
2041
    {
2042
        return $this->getTypeOfField($this->getFieldName($columnName));
2043
    }
2044
2045
    /**
2046
     * Gets the name of the primary table.
2047
     *
2048
     * @return string
2049
     */
2050 1376
    public function getTableName()
2051
    {
2052 1376
        return $this->table['name'];
2053
    }
2054
2055
    /**
2056
     * Gets primary table's schema name.
2057
     *
2058
     * @return string|null
2059
     */
2060 13
    public function getSchemaName()
2061
    {
2062 13
        return isset($this->table['schema']) ? $this->table['schema'] : null;
2063
    }
2064
2065
    /**
2066
     * Gets the table name to use for temporary identifier tables of this class.
2067
     *
2068
     * @return string
2069
     */
2070 7
    public function getTemporaryIdTableName()
2071
    {
2072
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
2073 7
        return str_replace('.', '_', $this->getTableName() . '_id_tmp');
2074
    }
2075
2076
    /**
2077
     * Sets the mapped subclasses of this class.
2078
     *
2079
     * @param array $subclasses The names of all mapped subclasses.
2080
     *
2081
     * @return void
2082
     */
2083 2
    public function setSubclasses(array $subclasses)
2084
    {
2085 2
        foreach ($subclasses as $subclass) {
2086 2
            $this->subClasses[] = $this->fullyQualifiedClassName($subclass);
2087
        }
2088 2
    }
2089
2090
    /**
2091
     * Sets the parent class names.
2092
     * Assumes that the class names in the passed array are in the order:
2093
     * directParent -> directParentParent -> directParentParentParent ... -> root.
2094
     *
2095
     * @param array $classNames
2096
     *
2097
     * @return void
2098
     */
2099 448
    public function setParentClasses(array $classNames)
2100
    {
2101 448
        $this->parentClasses = $classNames;
2102
2103 448
        if (count($classNames) > 0) {
2104 91
            $this->rootEntityName = array_pop($classNames);
2105
        }
2106 448
    }
2107
2108
    /**
2109
     * Sets the inheritance type used by the class and its subclasses.
2110
     *
2111
     * @param integer $type
2112
     *
2113
     * @return void
2114
     *
2115
     * @throws MappingException
2116
     */
2117 188
    public function setInheritanceType($type)
2118
    {
2119 188
        if ( ! $this->_isInheritanceType($type)) {
2120
            throw MappingException::invalidInheritanceType($this->name, $type);
2121
        }
2122
2123 188
        $this->inheritanceType = $type;
2124 188
    }
2125
2126
    /**
2127
     * Sets the association to override association mapping of property for an entity relationship.
2128
     *
2129
     * @param string $fieldName
2130
     * @param array  $overrideMapping
2131
     *
2132
     * @return void
2133
     *
2134
     * @throws MappingException
2135
     */
2136 26
    public function setAssociationOverride($fieldName, array $overrideMapping)
2137
    {
2138 26
        if ( ! isset($this->associationMappings[$fieldName])) {
2139 1
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2140
        }
2141
2142 25
        $mapping = $this->associationMappings[$fieldName];
2143
2144 25
        if (isset($overrideMapping['joinColumns'])) {
2145 13
            $mapping['joinColumns'] = $overrideMapping['joinColumns'];
2146
        }
2147
2148 25
        if (isset($overrideMapping['inversedBy'])) {
2149 6
            $mapping['inversedBy'] = $overrideMapping['inversedBy'];
2150
        }
2151
2152 25
        if (isset($overrideMapping['joinTable'])) {
2153 12
            $mapping['joinTable'] = $overrideMapping['joinTable'];
2154
        }
2155
2156 25
        if (isset($overrideMapping['fetch'])) {
2157 6
            $mapping['fetch'] = $overrideMapping['fetch'];
2158
        }
2159
2160 25
        $mapping['joinColumnFieldNames']        = null;
2161 25
        $mapping['joinTableColumns']            = null;
2162 25
        $mapping['sourceToTargetKeyColumns']    = null;
2163 25
        $mapping['relationToSourceKeyColumns']  = null;
2164 25
        $mapping['relationToTargetKeyColumns']  = null;
2165
2166 25
        switch ($mapping['type']) {
2167 25
            case self::ONE_TO_ONE:
2168 1
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2169 1
                break;
2170 24
            case self::ONE_TO_MANY:
2171
                $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2172
                break;
2173 24
            case self::MANY_TO_ONE:
2174 12
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2175 12
                break;
2176 24
            case self::MANY_TO_MANY:
2177 24
                $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2178 24
                break;
2179
        }
2180
2181 25
        $this->associationMappings[$fieldName] = $mapping;
2182 25
    }
2183
2184
    /**
2185
     * Sets the override for a mapped field.
2186
     *
2187
     * @param string $fieldName
2188
     * @param array  $overrideMapping
2189
     *
2190
     * @return void
2191
     *
2192
     * @throws MappingException
2193
     */
2194 15
    public function setAttributeOverride($fieldName, array $overrideMapping)
2195
    {
2196 15
        if ( ! isset($this->fieldMappings[$fieldName])) {
2197 1
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2198
        }
2199
2200 14
        $mapping = $this->fieldMappings[$fieldName];
2201
2202 14
        if (isset($mapping['id'])) {
2203 12
            $overrideMapping['id'] = $mapping['id'];
2204
        }
2205
2206 14
        if ( ! isset($overrideMapping['type'])) {
2207 6
            $overrideMapping['type'] = $mapping['type'];
2208
        }
2209
2210 14
        if ( ! isset($overrideMapping['fieldName'])) {
2211 5
            $overrideMapping['fieldName'] = $mapping['fieldName'];
2212
        }
2213
2214 14
        if ($overrideMapping['type'] !== $mapping['type']) {
2215 1
            throw MappingException::invalidOverrideFieldType($this->name, $fieldName);
2216
        }
2217
2218 13
        unset($this->fieldMappings[$fieldName]);
2219 13
        unset($this->fieldNames[$mapping['columnName']]);
2220 13
        unset($this->columnNames[$mapping['fieldName']]);
2221
2222 13
        $this->_validateAndCompleteFieldMapping($overrideMapping);
2223
2224 13
        $this->fieldMappings[$fieldName] = $overrideMapping;
2225 13
    }
2226
2227
    /**
2228
     * Checks whether a mapped field is inherited from an entity superclass.
2229
     *
2230
     * @param string $fieldName
2231
     *
2232
     * @return bool TRUE if the field is inherited, FALSE otherwise.
2233
     */
2234 406
    public function isInheritedField($fieldName)
2235
    {
2236 406
        return isset($this->fieldMappings[$fieldName]['inherited']);
2237
    }
2238
2239
    /**
2240
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
2241
     *
2242
     * @return bool
2243
     */
2244 447
    public function isRootEntity()
2245
    {
2246 447
        return $this->name == $this->rootEntityName;
2247
    }
2248
2249
    /**
2250
     * Checks whether a mapped association field is inherited from a superclass.
2251
     *
2252
     * @param string $fieldName
2253
     *
2254
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
2255
     */
2256 383
    public function isInheritedAssociation($fieldName)
2257
    {
2258 383
        return isset($this->associationMappings[$fieldName]['inherited']);
2259
    }
2260
2261 383
    public function isInheritedEmbeddedClass($fieldName)
2262
    {
2263 383
        return isset($this->embeddedClasses[$fieldName]['inherited']);
2264
    }
2265
2266
    /**
2267
     * Sets the name of the primary table the class is mapped to.
2268
     *
2269
     * @param string $tableName The table name.
2270
     *
2271
     * @return void
2272
     *
2273
     * @deprecated Use {@link setPrimaryTable}.
2274
     */
2275 5
    public function setTableName($tableName)
2276
    {
2277 5
        $this->table['name'] = $tableName;
2278 5
    }
2279
2280
    /**
2281
     * Sets the primary table definition. The provided array supports the
2282
     * following structure:
2283
     *
2284
     * name => <tableName> (optional, defaults to class name)
2285
     * indexes => array of indexes (optional)
2286
     * uniqueConstraints => array of constraints (optional)
2287
     *
2288
     * If a key is omitted, the current value is kept.
2289
     *
2290
     * @param array $table The table description.
2291
     *
2292
     * @return void
2293
     */
2294 346
    public function setPrimaryTable(array $table)
2295
    {
2296 346
        if (isset($table['name'])) {
2297
            // Split schema and table name from a table name like "myschema.mytable"
2298 280
            if (strpos($table['name'], '.') !== false) {
2299 9
                list($this->table['schema'], $table['name']) = explode('.', $table['name'], 2);
2300
            }
2301
2302 280
            if ($table['name'][0] === '`') {
2303 17
                $table['name']          = trim($table['name'], '`');
2304 17
                $this->table['quoted']  = true;
2305
            }
2306
2307 280
            $this->table['name'] = $table['name'];
2308
        }
2309
2310 346
        if (isset($table['quoted'])) {
2311 2
            $this->table['quoted'] = $table['quoted'];
2312
        }
2313
2314 346
        if (isset($table['schema'])) {
2315 6
            $this->table['schema'] = $table['schema'];
2316
        }
2317
2318 346
        if (isset($table['indexes'])) {
2319 18
            $this->table['indexes'] = $table['indexes'];
2320
        }
2321
2322 346
        if (isset($table['uniqueConstraints'])) {
2323 9
            $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
2324
        }
2325
2326 346
        if (isset($table['options'])) {
2327 11
            $this->table['options'] = $table['options'];
2328
        }
2329 346
    }
2330
2331
    /**
2332
     * Checks whether the given type identifies an inheritance type.
2333
     *
2334
     * @param integer $type
2335
     *
2336
     * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise.
2337
     */
2338 188
    private function _isInheritanceType($type)
2339
    {
2340 188
        return $type == self::INHERITANCE_TYPE_NONE ||
2341 115
                $type == self::INHERITANCE_TYPE_SINGLE_TABLE ||
2342 54
                $type == self::INHERITANCE_TYPE_JOINED ||
2343 188
                $type == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
2344
    }
2345
2346
    /**
2347
     * Adds a mapped field to the class.
2348
     *
2349
     * @param array $mapping The field mapping.
2350
     *
2351
     * @return void
2352
     *
2353
     * @throws MappingException
2354
     */
2355 575
    public function mapField(array $mapping)
2356
    {
2357 575
        $this->_validateAndCompleteFieldMapping($mapping);
2358 573
        $this->assertFieldNotMapped($mapping['fieldName']);
2359
2360 572
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2361 572
    }
2362
2363
    /**
2364
     * INTERNAL:
2365
     * Adds an association mapping without completing/validating it.
2366
     * This is mainly used to add inherited association mappings to derived classes.
2367
     *
2368
     * @param array $mapping
2369
     *
2370
     * @return void
2371
     *
2372
     * @throws MappingException
2373
     */
2374 58
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
2375
    {
2376 58
        if (isset($this->associationMappings[$mapping['fieldName']])) {
2377 1
            throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']);
2378
        }
2379 58
        $this->associationMappings[$mapping['fieldName']] = $mapping;
2380 58
    }
2381
2382
    /**
2383
     * INTERNAL:
2384
     * Adds a field mapping without completing/validating it.
2385
     * This is mainly used to add inherited field mappings to derived classes.
2386
     *
2387
     * @param array $fieldMapping
2388
     *
2389
     * @return void
2390
     */
2391 122
    public function addInheritedFieldMapping(array $fieldMapping)
2392
    {
2393 122
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
2394 122
        $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
2395 122
        $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
2396 122
    }
2397
2398
    /**
2399
     * INTERNAL:
2400
     * Adds a named query to this class.
2401
     *
2402
     * @param array $queryMapping
2403
     *
2404
     * @return void
2405
     *
2406
     * @throws MappingException
2407
     */
2408 36
    public function addNamedQuery(array $queryMapping)
2409
    {
2410 36
        if (!isset($queryMapping['name'])) {
2411 2
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2412
        }
2413
2414 34 View Code Duplication
        if (isset($this->namedQueries[$queryMapping['name']])) {
2415 1
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2416
        }
2417
2418 34
        if (!isset($queryMapping['query'])) {
2419
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2420
        }
2421
2422 34
        $name   = $queryMapping['name'];
2423 34
        $query  = $queryMapping['query'];
2424 34
        $dql    = str_replace('__CLASS__', $this->name, $query);
2425
2426 34
        $this->namedQueries[$name] = [
2427 34
            'name'  => $name,
2428 34
            'query' => $query,
2429 34
            'dql'   => $dql,
2430
        ];
2431 34
    }
2432
2433
    /**
2434
     * INTERNAL:
2435
     * Adds a named native query to this class.
2436
     *
2437
     * @param array $queryMapping
2438
     *
2439
     * @return void
2440
     *
2441
     * @throws MappingException
2442
     */
2443 41
    public function addNamedNativeQuery(array $queryMapping)
2444
    {
2445 41
        if (!isset($queryMapping['name'])) {
2446
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2447
        }
2448
2449 41 View Code Duplication
        if (isset($this->namedNativeQueries[$queryMapping['name']])) {
2450 1
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2451
        }
2452
2453 41
        if (!isset($queryMapping['query'])) {
2454
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2455
        }
2456
2457 41
        if (!isset($queryMapping['resultClass']) && !isset($queryMapping['resultSetMapping'])) {
2458
            throw MappingException::missingQueryMapping($this->name, $queryMapping['name']);
2459
        }
2460
2461 41
        $queryMapping['isSelfClass'] = false;
2462
2463 41
        if (isset($queryMapping['resultClass'])) {
2464 39
            if ($queryMapping['resultClass'] === '__CLASS__') {
2465
2466 12
                $queryMapping['isSelfClass'] = true;
2467 12
                $queryMapping['resultClass'] = $this->name;
2468
            }
2469
2470 39
            $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
2471 39
            $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\');
2472
        }
2473
2474 41
        $this->namedNativeQueries[$queryMapping['name']] = $queryMapping;
2475 41
    }
2476
2477
    /**
2478
     * INTERNAL:
2479
     * Adds a sql result set mapping to this class.
2480
     *
2481
     * @param array $resultMapping
2482
     *
2483
     * @return void
2484
     *
2485
     * @throws MappingException
2486
     */
2487 41
    public function addSqlResultSetMapping(array $resultMapping)
2488
    {
2489 41
        if (!isset($resultMapping['name'])) {
2490
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name);
2491
        }
2492
2493 41
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
2494 1
            throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']);
2495
        }
2496
2497 41
        if (isset($resultMapping['entities'])) {
2498 41
            foreach ($resultMapping['entities'] as $key => $entityResult) {
2499 41
                if (!isset($entityResult['entityClass'])) {
2500 1
                    throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']);
2501
                }
2502
2503 40
                $entityResult['isSelfClass'] = false;
2504 40
                if ($entityResult['entityClass'] === '__CLASS__') {
2505
2506 22
                    $entityResult['isSelfClass'] = true;
2507 22
                    $entityResult['entityClass'] = $this->name;
2508
2509
                }
2510
2511 40
                $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']);
2512
2513 40
                $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
2514 40
                $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
2515
2516 40
                if (isset($entityResult['fields'])) {
2517 36
                    foreach ($entityResult['fields'] as $k => $field) {
2518 36
                        if (!isset($field['name'])) {
2519
                            throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']);
2520
                        }
2521
2522 36
                        if (!isset($field['column'])) {
2523 16
                            $fieldName = $field['name'];
2524 16
                            if (strpos($fieldName, '.')) {
2525 10
                                list(, $fieldName) = explode('.', $fieldName);
2526
                            }
2527
2528 40
                            $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
2529
                        }
2530
                    }
2531
                }
2532
            }
2533
        }
2534
2535 40
        $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
2536 40
    }
2537
2538
    /**
2539
     * Adds a one-to-one mapping.
2540
     *
2541
     * @param array $mapping The mapping.
2542
     *
2543
     * @return void
2544
     */
2545 180
    public function mapOneToOne(array $mapping)
2546
    {
2547 180
        $mapping['type'] = self::ONE_TO_ONE;
2548
2549 180
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2550
2551 177
        $this->_storeAssociationMapping($mapping);
2552 176
    }
2553
2554
    /**
2555
     * Adds a one-to-many mapping.
2556
     *
2557
     * @param array $mapping The mapping.
2558
     *
2559
     * @return void
2560
     */
2561 141
    public function mapOneToMany(array $mapping)
2562
    {
2563 141
        $mapping['type'] = self::ONE_TO_MANY;
2564
2565 141
        $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2566
2567 140
        $this->_storeAssociationMapping($mapping);
2568 140
    }
2569
2570
    /**
2571
     * Adds a many-to-one mapping.
2572
     *
2573
     * @param array $mapping The mapping.
2574
     *
2575
     * @return void
2576
     */
2577 167
    public function mapManyToOne(array $mapping)
2578
    {
2579 167
        $mapping['type'] = self::MANY_TO_ONE;
2580
2581
        // A many-to-one mapping is essentially a one-one backreference
2582 167
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2583
2584 162
        $this->_storeAssociationMapping($mapping);
2585 162
    }
2586
2587
    /**
2588
     * Adds a many-to-many mapping.
2589
     *
2590
     * @param array $mapping The mapping.
2591
     *
2592
     * @return void
2593
     */
2594 166
    public function mapManyToMany(array $mapping)
2595
    {
2596 166
        $mapping['type'] = self::MANY_TO_MANY;
2597
2598 166
        $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2599
2600 164
        $this->_storeAssociationMapping($mapping);
2601 164
    }
2602
2603
    /**
2604
     * Stores the association mapping.
2605
     *
2606
     * @param array $assocMapping
2607
     *
2608
     * @return void
2609
     *
2610
     * @throws MappingException
2611
     */
2612 362
    protected function _storeAssociationMapping(array $assocMapping)
2613
    {
2614 362
        $sourceFieldName = $assocMapping['fieldName'];
2615
2616 362
        $this->assertFieldNotMapped($sourceFieldName);
2617
2618 361
        $this->associationMappings[$sourceFieldName] = $assocMapping;
2619 361
    }
2620
2621
    /**
2622
     * Registers a custom repository class for the entity class.
2623
     *
2624
     * @param string $repositoryClassName The class name of the custom mapper.
2625
     *
2626
     * @return void
2627
     */
2628 65
    public function setCustomRepositoryClass($repositoryClassName)
2629
    {
2630 65
        $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
2631 65
    }
2632
2633
    /**
2634
     * Dispatches the lifecycle event of the given entity to the registered
2635
     * lifecycle callbacks and lifecycle listeners.
2636
     *
2637
     * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker
2638
     *
2639
     * @param string $lifecycleEvent The lifecycle event.
2640
     * @param object $entity         The Entity on which the event occurred.
2641
     *
2642
     * @return void
2643
     */
2644 1
    public function invokeLifecycleCallbacks($lifecycleEvent, $entity)
2645
    {
2646 1
        foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) {
2647 1
            $entity->$callback();
2648
        }
2649 1
    }
2650
2651
    /**
2652
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
2653
     *
2654
     * @param string $lifecycleEvent
2655
     *
2656
     * @return boolean
2657
     */
2658
    public function hasLifecycleCallbacks($lifecycleEvent)
2659
    {
2660
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
2661
    }
2662
2663
    /**
2664
     * Gets the registered lifecycle callbacks for an event.
2665
     *
2666
     * @param string $event
2667
     *
2668
     * @return array
2669
     */
2670
    public function getLifecycleCallbacks($event)
2671
    {
2672
        return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : [];
2673
    }
2674
2675
    /**
2676
     * Adds a lifecycle callback for entities of this class.
2677
     *
2678
     * @param string $callback
2679
     * @param string $event
2680
     *
2681
     * @return void
2682
     */
2683 47
    public function addLifecycleCallback($callback, $event)
2684
    {
2685 47
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
2686 3
            return;
2687
        }
2688
2689 47
        $this->lifecycleCallbacks[$event][] = $callback;
2690 47
    }
2691
2692
    /**
2693
     * Sets the lifecycle callbacks for entities of this class.
2694
     * Any previously registered callbacks are overwritten.
2695
     *
2696
     * @param array $callbacks
2697
     *
2698
     * @return void
2699
     */
2700 137
    public function setLifecycleCallbacks(array $callbacks)
2701
    {
2702 137
        $this->lifecycleCallbacks = $callbacks;
2703 137
    }
2704
2705
    /**
2706
     * Adds a entity listener for entities of this class.
2707
     *
2708
     * @param string $eventName The entity lifecycle event.
2709
     * @param string $class     The listener class.
2710
     * @param string $method    The listener callback method.
2711
     *
2712
     * @throws \Doctrine\ORM\Mapping\MappingException
2713
     */
2714 35
    public function addEntityListener($eventName, $class, $method)
2715
    {
2716 35
        $class    = $this->fullyQualifiedClassName($class);
2717
2718
        $listener = [
2719 35
            'class'  => $class,
2720 35
            'method' => $method,
2721
        ];
2722
2723 35
        if ( ! class_exists($class)) {
2724 1
            throw MappingException::entityListenerClassNotFound($class, $this->name);
2725
        }
2726
2727 34
        if ( ! method_exists($class, $method)) {
2728 1
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->name);
2729
        }
2730
2731 33
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName])) {
2732 1
            throw MappingException::duplicateEntityListener($class, $method, $this->name);
2733
        }
2734
2735 33
        $this->entityListeners[$eventName][] = $listener;
2736 33
    }
2737
2738
    /**
2739
     * Sets the discriminator column definition.
2740
     *
2741
     * @param array $columnDef
2742
     *
2743
     * @return void
2744
     *
2745
     * @throws MappingException
2746
     *
2747
     * @see getDiscriminatorColumn()
2748
     */
2749 180
    public function setDiscriminatorColumn($columnDef)
2750
    {
2751 180
        if ($columnDef !== null) {
2752 119
            if ( ! isset($columnDef['name'])) {
2753 1
                throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name);
2754
            }
2755
2756 118
            if (isset($this->fieldNames[$columnDef['name']])) {
2757 1
                throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
2758
            }
2759
2760 117
            if ( ! isset($columnDef['fieldName'])) {
2761 112
                $columnDef['fieldName'] = $columnDef['name'];
2762
            }
2763
2764 117
            if ( ! isset($columnDef['type'])) {
2765 2
                $columnDef['type'] = "string";
2766
            }
2767
2768 117
            if (in_array($columnDef['type'], ["boolean", "array", "object", "datetime", "time", "date"])) {
2769
                throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
2770
            }
2771
2772 117
            $this->discriminatorColumn = $columnDef;
2773
        }
2774 178
    }
2775
2776
    /**
2777
     * Sets the discriminator values used by this class.
2778
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
2779
     *
2780
     * @param array $map
2781
     *
2782
     * @return void
2783
     */
2784 172
    public function setDiscriminatorMap(array $map)
2785
    {
2786 172
        foreach ($map as $value => $className) {
2787 112
            $this->addDiscriminatorMapClass($value, $className);
2788
        }
2789 172
    }
2790
2791
    /**
2792
     * Adds one entry of the discriminator map with a new class and corresponding name.
2793
     *
2794
     * @param string $name
2795
     * @param string $className
2796
     *
2797
     * @return void
2798
     *
2799
     * @throws MappingException
2800
     */
2801 113
    public function addDiscriminatorMapClass($name, $className)
2802
    {
2803 113
        $className = $this->fullyQualifiedClassName($className);
2804 113
        $className = ltrim($className, '\\');
2805
2806 113
        $this->discriminatorMap[$name] = $className;
2807
2808 113
        if ($this->name === $className) {
2809 84
            $this->discriminatorValue = $name;
2810
2811 84
            return;
2812
        }
2813
2814 112
        if ( ! (class_exists($className) || interface_exists($className))) {
2815
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
2816
        }
2817
2818 112
        if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) {
2819 103
            $this->subClasses[] = $className;
2820
        }
2821 112
    }
2822
2823
    /**
2824
     * Checks whether the class has a named query with the given query name.
2825
     *
2826
     * @param string $queryName
2827
     *
2828
     * @return boolean
2829
     */
2830 1
    public function hasNamedQuery($queryName)
2831
    {
2832 1
        return isset($this->namedQueries[$queryName]);
2833
    }
2834
2835
    /**
2836
     * Checks whether the class has a named native query with the given query name.
2837
     *
2838
     * @param string $queryName
2839
     *
2840
     * @return boolean
2841
     */
2842 1
    public function hasNamedNativeQuery($queryName)
2843
    {
2844 1
        return isset($this->namedNativeQueries[$queryName]);
2845
    }
2846
2847
    /**
2848
     * Checks whether the class has a named native query with the given query name.
2849
     *
2850
     * @param string $name
2851
     *
2852
     * @return boolean
2853
     */
2854 1
    public function hasSqlResultSetMapping($name)
2855
    {
2856 1
        return isset($this->sqlResultSetMappings[$name]);
2857
    }
2858
2859
    /**
2860
     * {@inheritDoc}
2861
     */
2862 348
    public function hasAssociation($fieldName)
2863
    {
2864 348
        return isset($this->associationMappings[$fieldName]);
2865
    }
2866
2867
    /**
2868
     * {@inheritDoc}
2869
     */
2870 1
    public function isSingleValuedAssociation($fieldName)
2871
    {
2872 1
        return isset($this->associationMappings[$fieldName])
2873 1
            && ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2874
    }
2875
2876
    /**
2877
     * {@inheritDoc}
2878
     */
2879 1049
    public function isCollectionValuedAssociation($fieldName)
2880
    {
2881 1049
        return isset($this->associationMappings[$fieldName])
2882 1049
            && ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2883
    }
2884
2885
    /**
2886
     * Is this an association that only has a single join column?
2887
     *
2888
     * @param string $fieldName
2889
     *
2890
     * @return bool
2891
     */
2892 98
    public function isAssociationWithSingleJoinColumn($fieldName)
2893
    {
2894 98
        return isset($this->associationMappings[$fieldName])
2895 98
            && isset($this->associationMappings[$fieldName]['joinColumns'][0])
2896 98
            && ! isset($this->associationMappings[$fieldName]['joinColumns'][1]);
2897
    }
2898
2899
    /**
2900
     * Returns the single association join column (if any).
2901
     *
2902
     * @param string $fieldName
2903
     *
2904
     * @return string
2905
     *
2906
     * @throws MappingException
2907
     */
2908 9 View Code Duplication
    public function getSingleAssociationJoinColumnName($fieldName)
2909
    {
2910 9
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2911
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2912
        }
2913
2914 9
        return $this->associationMappings[$fieldName]['joinColumns'][0]['name'];
2915
    }
2916
2917
    /**
2918
     * Returns the single association referenced join column name (if any).
2919
     *
2920
     * @param string $fieldName
2921
     *
2922
     * @return string
2923
     *
2924
     * @throws MappingException
2925
     */
2926 9 View Code Duplication
    public function getSingleAssociationReferencedJoinColumnName($fieldName)
2927
    {
2928 9
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2929
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2930
        }
2931
2932 9
        return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName'];
2933
    }
2934
2935
    /**
2936
     * Used to retrieve a fieldname for either field or association from a given column.
2937
     *
2938
     * This method is used in foreign-key as primary-key contexts.
2939
     *
2940
     * @param string $columnName
2941
     *
2942
     * @return string
2943
     *
2944
     * @throws MappingException
2945
     */
2946 669
    public function getFieldForColumn($columnName)
2947
    {
2948 669
        if (isset($this->fieldNames[$columnName])) {
2949 646
            return $this->fieldNames[$columnName];
2950
        }
2951
2952 97
        foreach ($this->associationMappings as $assocName => $mapping) {
2953 97
            if ($this->isAssociationWithSingleJoinColumn($assocName) &&
2954 97
                $this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) {
2955
2956 97
                return $assocName;
2957
            }
2958
        }
2959
2960
        throw MappingException::noFieldNameFoundForColumn($this->name, $columnName);
2961
    }
2962
2963
    /**
2964
     * Sets the ID generator used to generate IDs for instances of this class.
2965
     *
2966
     * @param \Doctrine\ORM\Id\AbstractIdGenerator $generator
2967
     *
2968
     * @return void
2969
     */
2970 450
    public function setIdGenerator($generator)
2971
    {
2972 450
        $this->idGenerator = $generator;
2973 450
    }
2974
2975
    /**
2976
     * Sets definition.
2977
     *
2978
     * @param array $definition
2979
     *
2980
     * @return void
2981
     */
2982 13
    public function setCustomGeneratorDefinition(array $definition)
2983
    {
2984 13
        $this->customGeneratorDefinition = $definition;
2985 13
    }
2986
2987
    /**
2988
     * Sets the definition of the sequence ID generator for this class.
2989
     *
2990
     * The definition must have the following structure:
2991
     * <code>
2992
     * array(
2993
     *     'sequenceName'   => 'name',
2994
     *     'allocationSize' => 20,
2995
     *     'initialValue'   => 1
2996
     *     'quoted'         => 1
2997
     * )
2998
     * </code>
2999
     *
3000
     * @param array $definition
3001
     *
3002
     * @return void
3003
     *
3004
     * @throws MappingException
3005
     */
3006 29
    public function setSequenceGeneratorDefinition(array $definition)
3007
    {
3008 29
        if ( ! isset($definition['sequenceName'])) {
3009 1
            throw MappingException::missingSequenceName($this->name);
3010
        }
3011
3012 28
        if ($definition['sequenceName'][0] == '`') {
3013 1
            $definition['sequenceName']   = trim($definition['sequenceName'], '`');
3014 1
            $definition['quoted'] = true;
3015
        }
3016
3017 28
        $this->sequenceGeneratorDefinition = $definition;
3018 28
    }
3019
3020
    /**
3021
     * Sets the version field mapping used for versioning. Sets the default
3022
     * value to use depending on the column type.
3023
     *
3024
     * @param array $mapping The version field mapping array.
3025
     *
3026
     * @return void
3027
     *
3028
     * @throws MappingException
3029
     */
3030 33
    public function setVersionMapping(array &$mapping)
3031
    {
3032 33
        $this->isVersioned = true;
3033 33
        $this->versionField = $mapping['fieldName'];
3034
3035 33
        if ( ! isset($mapping['default'])) {
3036 33
            if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'])) {
3037 32
                $mapping['default'] = 1;
3038 2
            } else if ($mapping['type'] == 'datetime') {
3039 1
                $mapping['default'] = 'CURRENT_TIMESTAMP';
3040
            } else {
3041 1
                throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']);
3042
            }
3043
        }
3044 32
    }
3045
3046
    /**
3047
     * Sets whether this class is to be versioned for optimistic locking.
3048
     *
3049
     * @param boolean $bool
3050
     *
3051
     * @return void
3052
     */
3053 137
    public function setVersioned($bool)
3054
    {
3055 137
        $this->isVersioned = $bool;
3056 137
    }
3057
3058
    /**
3059
     * Sets the name of the field that is to be used for versioning if this class is
3060
     * versioned for optimistic locking.
3061
     *
3062
     * @param string $versionField
3063
     *
3064
     * @return void
3065
     */
3066 137
    public function setVersionField($versionField)
3067
    {
3068 137
        $this->versionField = $versionField;
3069 137
    }
3070
3071
    /**
3072
     * Marks this class as read only, no change tracking is applied to it.
3073
     *
3074
     * @return void
3075
     */
3076 3
    public function markReadOnly()
3077
    {
3078 3
        $this->isReadOnly = true;
3079 3
    }
3080
3081
    /**
3082
     * {@inheritDoc}
3083
     */
3084
    public function getFieldNames()
3085
    {
3086
        return array_keys($this->fieldMappings);
3087
    }
3088
3089
    /**
3090
     * {@inheritDoc}
3091
     */
3092
    public function getAssociationNames()
3093
    {
3094
        return array_keys($this->associationMappings);
3095
    }
3096
3097
    /**
3098
     * {@inheritDoc}
3099
     *
3100
     * @throws InvalidArgumentException
3101
     */
3102 1
    public function getAssociationTargetClass($assocName)
3103
    {
3104 1
        if ( ! isset($this->associationMappings[$assocName])) {
3105
            throw new InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
3106
        }
3107
3108 1
        return $this->associationMappings[$assocName]['targetEntity'];
3109
    }
3110
3111
    /**
3112
     * {@inheritDoc}
3113
     */
3114 761
    public function getName()
3115
    {
3116 761
        return $this->name;
3117
    }
3118
3119
    /**
3120
     * Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
3121
     *
3122
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3123
     *
3124
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3125
     *
3126
     * @return array
3127
     */
3128
    public function getQuotedIdentifierColumnNames($platform)
3129
    {
3130
        $quotedColumnNames = [];
3131
3132
        foreach ($this->identifier as $idProperty) {
3133
            if (isset($this->fieldMappings[$idProperty])) {
3134
                $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
3135
                    ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
3136
                    : $this->fieldMappings[$idProperty]['columnName'];
3137
3138
                continue;
3139
            }
3140
3141
            // Association defined as Id field
3142
            $joinColumns            = $this->associationMappings[$idProperty]['joinColumns'];
3143
            $assocQuotedColumnNames = array_map(
3144 View Code Duplication
                function ($joinColumn) use ($platform) {
3145
                    return isset($joinColumn['quoted'])
3146
                        ? $platform->quoteIdentifier($joinColumn['name'])
3147
                        : $joinColumn['name'];
3148
                },
3149
                $joinColumns
3150
            );
3151
3152
            $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
3153
        }
3154
3155
        return $quotedColumnNames;
3156
    }
3157
3158
    /**
3159
     * Gets the (possibly quoted) column name of a mapped field for safe use  in an SQL statement.
3160
     *
3161
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3162
     *
3163
     * @param string                                    $field
3164
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3165
     *
3166
     * @return string
3167
     */
3168
    public function getQuotedColumnName($field, $platform)
3169
    {
3170
        return isset($this->fieldMappings[$field]['quoted'])
3171
            ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName'])
3172
            : $this->fieldMappings[$field]['columnName'];
3173
    }
3174
3175
    /**
3176
     * Gets the (possibly quoted) primary table name of this class for safe use in an SQL statement.
3177
     *
3178
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3179
     *
3180
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3181
     *
3182
     * @return string
3183
     */
3184
    public function getQuotedTableName($platform)
3185
    {
3186
        return isset($this->table['quoted'])
3187
            ? $platform->quoteIdentifier($this->table['name'])
3188
            : $this->table['name'];
3189
    }
3190
3191
    /**
3192
     * Gets the (possibly quoted) name of the join table.
3193
     *
3194
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3195
     *
3196
     * @param array                                     $assoc
3197
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3198
     *
3199
     * @return string
3200
     */
3201
    public function getQuotedJoinTableName(array $assoc, $platform)
3202
    {
3203
        return isset($assoc['joinTable']['quoted'])
3204
            ? $platform->quoteIdentifier($assoc['joinTable']['name'])
3205
            : $assoc['joinTable']['name'];
3206
    }
3207
3208
    /**
3209
     * {@inheritDoc}
3210
     */
3211 12
    public function isAssociationInverseSide($fieldName)
3212
    {
3213 12
        return isset($this->associationMappings[$fieldName])
3214 12
            && ! $this->associationMappings[$fieldName]['isOwningSide'];
3215
    }
3216
3217
    /**
3218
     * {@inheritDoc}
3219
     */
3220
    public function getAssociationMappedByTargetField($fieldName)
3221
    {
3222
        return $this->associationMappings[$fieldName]['mappedBy'];
3223
    }
3224
3225
    /**
3226
     * @param string $targetClass
3227
     *
3228
     * @return array
3229
     */
3230 2
    public function getAssociationsByTargetClass($targetClass)
3231
    {
3232 2
        $relations = [];
3233
3234 2
        foreach ($this->associationMappings as $mapping) {
3235 2
            if ($mapping['targetEntity'] == $targetClass) {
3236 2
                $relations[$mapping['fieldName']] = $mapping;
3237
            }
3238
        }
3239
3240 2
        return $relations;
3241
    }
3242
3243
    /**
3244
     * @param  string|null $className
3245
     *
3246
     * @return string|null null if the input value is null
3247
     */
3248 506
    public function fullyQualifiedClassName($className)
3249
    {
3250 506
        if (empty($className)) {
3251 49
            return $className;
3252
        }
3253
3254 490
        if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
3255 374
            return $this->namespace . '\\' . $className;
3256
        }
3257
3258 249
        return $className;
3259
    }
3260
3261
    /**
3262
     * @param string $name
3263
     *
3264
     * @return mixed
3265
     */
3266 2
    public function getMetadataValue($name)
3267
    {
3268
3269 2
        if (isset($this->$name)) {
3270 2
            return $this->$name;
3271
        }
3272
3273
        return null;
3274
    }
3275
3276
    /**
3277
     * Map Embedded Class
3278
     *
3279
     * @param array $mapping
3280
     *
3281
     * @throws MappingException
3282
     * @return void
3283
     */
3284 31
    public function mapEmbedded(array $mapping)
3285
    {
3286 31
        $this->assertFieldNotMapped($mapping['fieldName']);
3287
3288 31
        $this->embeddedClasses[$mapping['fieldName']] = [
3289 31
            'class' => $this->fullyQualifiedClassName($mapping['class']),
3290 31
            'columnPrefix' => $mapping['columnPrefix'],
3291 31
            'declaredField' => isset($mapping['declaredField']) ? $mapping['declaredField'] : null,
3292 31
            'originalField' => isset($mapping['originalField']) ? $mapping['originalField'] : null,
3293
        ];
3294 31
    }
3295
3296
    /**
3297
     * Inline the embeddable class
3298
     *
3299
     * @param string            $property
3300
     * @param ClassMetadataInfo $embeddable
3301
     */
3302 12
    public function inlineEmbeddable($property, ClassMetadataInfo $embeddable)
3303
    {
3304 12
        foreach ($embeddable->fieldMappings as $fieldMapping) {
3305 12
            $fieldMapping['originalClass'] = isset($fieldMapping['originalClass'])
3306 4
                ? $fieldMapping['originalClass']
3307 12
                : $embeddable->name;
3308 12
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
3309 4
                ? $property . '.' . $fieldMapping['declaredField']
3310 12
                : $property;
3311 12
            $fieldMapping['originalField'] = isset($fieldMapping['originalField'])
3312 4
                ? $fieldMapping['originalField']
3313 12
                : $fieldMapping['fieldName'];
3314 12
            $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName'];
3315
3316 12
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
3317 2
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
3318 11
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
3319 8
                $fieldMapping['columnName'] = $this->namingStrategy
3320 8
                    ->embeddedFieldToColumnName(
3321 8
                        $property,
3322 8
                        $fieldMapping['columnName'],
3323 8
                        $this->reflClass->name,
3324 8
                        $embeddable->reflClass->name
3325
                    );
3326
            }
3327
3328 12
            $this->mapField($fieldMapping);
3329
        }
3330 12
    }
3331
3332
    /**
3333
     * @param string $fieldName
3334
     * @throws MappingException
3335
     */
3336 611
    private function assertFieldNotMapped($fieldName)
3337
    {
3338 611
        if (isset($this->fieldMappings[$fieldName]) ||
3339 611
            isset($this->associationMappings[$fieldName]) ||
3340 611
            isset($this->embeddedClasses[$fieldName])) {
3341
3342 2
            throw MappingException::duplicateFieldMapping($this->name, $fieldName);
3343
        }
3344 611
    }
3345
3346
    /**
3347
     * Gets the sequence name based on class metadata.
3348
     *
3349
     * @param AbstractPlatform $platform
3350
     * @return string
3351
     *
3352
     * @todo Sequence names should be computed in DBAL depending on the platform
3353
     */
3354 3
    public function getSequenceName(AbstractPlatform $platform)
3355
    {
3356 3
        $sequencePrefix = $this->getSequencePrefix($platform);
3357 3
        $columnName     = $this->getSingleIdentifierColumnName();
3358 3
        $sequenceName   = $sequencePrefix . '_' . $columnName . '_seq';
3359
3360 3
        return $sequenceName;
3361
    }
3362
3363
    /**
3364
     * Gets the sequence name prefix based on class metadata.
3365
     *
3366
     * @param AbstractPlatform $platform
3367
     * @return string
3368
     *
3369
     * @todo Sequence names should be computed in DBAL depending on the platform
3370
     */
3371 3
    public function getSequencePrefix(AbstractPlatform $platform)
3372
    {
3373 3
        $tableName      = $this->getTableName();
3374 3
        $sequencePrefix = $tableName;
3375
3376
        // Prepend the schema name to the table name if there is one
3377 3
        if ($schemaName = $this->getSchemaName()) {
3378 3
            $sequencePrefix = $schemaName . '.' . $tableName;
3379
3380 3
            if ( ! $platform->supportsSchemas() && $platform->canEmulateSchemas()) {
3381 3
                $sequencePrefix = $schemaName . '__' . $tableName;
3382
            }
3383
        }
3384
3385 3
        return $sequencePrefix;
3386
    }
3387
3388
    /**
3389
     * @param array $mapping
3390
     */
3391 228
    private function assertMappingOrderBy(array $mapping)
3392
    {
3393 228
        if (isset($mapping['orderBy']) && !is_array($mapping['orderBy'])) {
3394
            throw new InvalidArgumentException("'orderBy' is expected to be an array, not " . gettype($mapping['orderBy']));
3395
        }
3396 228
    }
3397
}
3398