Failed Conditions
Push — master ( a0c0d3...57a950 )
by Marco
22:07
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 701
    public function __construct($entityName, NamingStrategy $namingStrategy = null)
660
    {
661 701
        $this->name = $entityName;
662 701
        $this->rootEntityName = $entityName;
663 701
        $this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy();
664 701
        $this->instantiator   = new Instantiator();
665 701
    }
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 700
    public function newInstance()
911
    {
912 700
        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 2070
    public function wakeupReflection($reflService)
923
    {
924
        // Restore ReflectionClass and properties
925 2070
        $this->reflClass    = $reflService->getClass($this->name);
926 2070
        $this->instantiator = $this->instantiator ?: new Instantiator();
927
928 2070
        $parentReflFields = [];
929
930 2070
        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(
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 2070
        foreach ($this->fieldMappings as $field => $mapping) {
954 2065
            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']),
958 22
                    $mapping['originalClass']
959
                );
960 22
                continue;
961
            }
962
963 2065
            $this->reflFields[$field] = isset($mapping['declared'])
964 524
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
965 2065
                : $reflService->getAccessibleProperty($this->name, $field);
966
        }
967
968 2070
        foreach ($this->associationMappings as $field => $mapping) {
969 1729
            $this->reflFields[$field] = isset($mapping['declared'])
970 425
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
971 1729
                : $reflService->getAccessibleProperty($this->name, $field);
972
        }
973 2070
    }
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 662
    public function initializeReflection($reflService)
984
    {
985 662
        $this->reflClass = $reflService->getClass($this->name);
986 662
        $this->namespace = $reflService->getClassNamespace($this->name);
987
988 662
        if ($this->reflClass) {
989 655
            $this->name = $this->rootEntityName = $this->reflClass->getName();
990
        }
991
992 662
        $this->table['name'] = $this->namingStrategy->classToTableName($this->name);
993 662
    }
994
995
    /**
996
     * Validates Identifier.
997
     *
998
     * @return void
999
     *
1000
     * @throws MappingException
1001
     */
1002 444
    public function validateIdentifier()
1003
    {
1004 444
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
1005 63
            return;
1006
        }
1007
1008
        // Verify & complete identifier mapping
1009 442
        if ( ! $this->identifier) {
1010 6
            throw MappingException::identifierRequired($this->name);
1011
        }
1012
1013 436
        if ($this->usesIdGenerator() && $this->isIdentifierComposite) {
1014
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name);
1015
        }
1016 436
    }
1017
1018
    /**
1019
     * Validates association targets actually exist.
1020
     *
1021
     * @return void
1022
     *
1023
     * @throws MappingException
1024
     */
1025 445
    public function validateAssociations()
1026
    {
1027 445
        foreach ($this->associationMappings as $mapping) {
1028 288
            if ( ! ClassLoader::classExists($mapping['targetEntity']) ) {
1029 288
                throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']);
1030
            }
1031
        }
1032 444
    }
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 445
    public function validateLifecycleCallbacks($reflService)
1044
    {
1045 445
        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 444
    }
1053
1054
    /**
1055
     * {@inheritDoc}
1056
     */
1057 576
    public function getReflectionClass()
1058
    {
1059 576
        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 156
    public function setChangeTrackingPolicy($policy)
1120
    {
1121 156
        $this->changeTrackingPolicy = $policy;
1122 156
    }
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 304
    public function isChangeTrackingNotify()
1150
    {
1151 304
        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 1103
    public function isIdentifier($fieldName)
1163
    {
1164 1103
        if ( ! $this->identifier) {
1165 1
            return false;
1166
        }
1167
1168 1102
        if ( ! $this->isIdentifierComposite) {
1169 1097
            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 207 View Code Duplication
    public function getFieldMapping($fieldName)
1230
    {
1231 207
        if ( ! isset($this->fieldMappings[$fieldName])) {
1232 1
            throw MappingException::mappingNotFound($this->name, $fieldName);
1233
        }
1234
1235 206
        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 498 View Code Duplication
    public function getAssociationMapping($fieldName)
1251
    {
1252 498
        if ( ! isset($this->associationMappings[$fieldName])) {
1253
            throw MappingException::mappingNotFound($this->name, $fieldName);
1254
        }
1255
1256 498
        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 247
    public function getFieldName($columnName)
1278
    {
1279 247
        return isset($this->fieldNames[$columnName])
1280 247
            ? $this->fieldNames[$columnName]
1281 247
            : $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 580
    protected function _validateAndCompleteFieldMapping(array &$mapping)
1384
    {
1385
        // Check mandatory fields
1386 580 View Code Duplication
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
1387 1
            throw MappingException::missingFieldName($this->name);
1388
        }
1389
1390 579
        if ( ! isset($mapping['type'])) {
1391
            // Default to string
1392 64
            $mapping['type'] = 'string';
1393
        }
1394
1395
        // Complete fieldName and columnName mapping
1396 579
        if ( ! isset($mapping['columnName'])) {
1397 482
            $mapping['columnName'] = $this->namingStrategy->propertyToColumnName($mapping['fieldName'], $this->name);
1398
        }
1399
1400 579
        if ('`' === $mapping['columnName'][0]) {
1401 11
            $mapping['columnName']  = trim($mapping['columnName'], '`');
1402 11
            $mapping['quoted']      = true;
1403
        }
1404
1405 579
        $this->columnNames[$mapping['fieldName']] = $mapping['columnName'];
1406
1407 579
        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 578
        $this->fieldNames[$mapping['columnName']] = $mapping['fieldName'];
1412
1413
        // Complete id mapping
1414 578
        if (isset($mapping['id']) && true === $mapping['id']) {
1415 538
            if ($this->versionField == $mapping['fieldName']) {
1416
                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
1417
            }
1418
1419 538
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1420 538
                $this->identifier[] = $mapping['fieldName'];
1421
            }
1422
1423
            // Check for composite key
1424 538 View Code Duplication
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1425 22
                $this->isIdentifierComposite = true;
1426
            }
1427
        }
1428
1429 578
        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 578
    }
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 377
    protected function _validateAndCompleteAssociationMapping(array $mapping)
1449
    {
1450 377
        if ( ! isset($mapping['mappedBy'])) {
1451 363
            $mapping['mappedBy'] = null;
1452
        }
1453
1454 377
        if ( ! isset($mapping['inversedBy'])) {
1455 347
            $mapping['inversedBy'] = null;
1456
        }
1457
1458 377
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
1459
1460 377
        if (empty($mapping['indexBy'])) {
1461 374
            unset($mapping['indexBy']);
1462
        }
1463
1464
        // If targetEntity is unqualified, assume it is in the same namespace as
1465
        // the sourceEntity.
1466 377
        $mapping['sourceEntity'] = $this->name;
1467
1468 377
        if (isset($mapping['targetEntity'])) {
1469 377
            $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
1470 377
            $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
1471
        }
1472
1473 377 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 376
        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 372 View Code Duplication
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
1507
            throw MappingException::missingFieldName($this->name);
1508
        }
1509
1510 372
        if ( ! isset($mapping['targetEntity'])) {
1511
            throw MappingException::missingTargetEntity($mapping['fieldName']);
1512
        }
1513
1514
        // Mandatory and optional attributes for either side
1515 372
        if ( ! $mapping['mappedBy']) {
1516 358
            if (isset($mapping['joinTable']) && $mapping['joinTable']) {
1517 139
                if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
1518 4
                    $mapping['joinTable']['name']   = trim($mapping['joinTable']['name'], '`');
1519 358
                    $mapping['joinTable']['quoted'] = true;
1520
                }
1521
            }
1522
        } else {
1523 198
            $mapping['isOwningSide'] = false;
1524
        }
1525
1526 372 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 369
        if ( ! isset($mapping['fetch'])) {
1532 104
            $mapping['fetch'] = self::FETCH_LAZY;
1533
        }
1534
1535
        // Cascades
1536 369
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
1537
1538 369
        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1539 369
        if (in_array('all', $cascades)) {
1540 41
            $cascades = $allCascades;
1541 362
        } 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 368
        $mapping['cascade'] = $cascades;
1550 368
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1551 368
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1552 368
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1553 368
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1554 368
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1555
1556 368
        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 318
    protected function _validateAndCompleteOneToOneMapping(array $mapping)
1570
    {
1571 318
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1572
1573 312
        if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
1574 227
            $mapping['isOwningSide'] = true;
1575
        }
1576
1577 312
        if ($mapping['isOwningSide']) {
1578 299
            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 299
            $uniqueConstraintColumns = [];
1589
1590 299
            foreach ($mapping['joinColumns'] as &$joinColumn) {
1591 299
                if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
1592 165
                    if (count($mapping['joinColumns']) === 1) {
1593 163
                        if (empty($mapping['id'])) {
1594 163
                            $joinColumn['unique'] = true;
1595
                        }
1596
                    } else {
1597 2
                        $uniqueConstraintColumns[] = $joinColumn['name'];
1598
                    }
1599
                }
1600
1601 299
                if (empty($joinColumn['name'])) {
1602 34
                    $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name);
1603
                }
1604
1605 299
                if (empty($joinColumn['referencedColumnName'])) {
1606 5
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1607
                }
1608
1609 299
                if ($joinColumn['name'][0] === '`') {
1610 7
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1611 7
                    $joinColumn['quoted'] = true;
1612
                }
1613
1614 299
                if ($joinColumn['referencedColumnName'][0] === '`') {
1615 4
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1616 4
                    $joinColumn['quoted']               = true;
1617
                }
1618
1619 299
                $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1620 299
                $mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName'])
1621
                    ? $joinColumn['fieldName']
1622 299
                    : $joinColumn['name'];
1623
            }
1624
1625 299
            if ($uniqueConstraintColumns) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uniqueConstraintColumns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1626 2
                if ( ! $this->table) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->table of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
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 299
            $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
1636
        }
1637
1638 312
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1639 312
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1640
1641 312
        if ($mapping['orphanRemoval']) {
1642 22
            unset($mapping['unique']);
1643
        }
1644
1645 312 View Code Duplication
        if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
1646 2
            throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
1647
        }
1648
1649 310
        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 142
    protected function _validateAndCompleteOneToManyMapping(array $mapping)
1663
    {
1664 142
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1665
1666
        // OneToMany-side MUST be inverse (must have mappedBy)
1667 141
        if ( ! isset($mapping['mappedBy'])) {
1668
            throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
1669
        }
1670
1671 141
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1672 141
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1673
1674 141
        $this->assertMappingOrderBy($mapping);
1675
1676 141
        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 169
    protected function _validateAndCompleteManyToManyMapping(array $mapping)
1689
    {
1690 169
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1691
1692 167
        if ($mapping['isOwningSide']) {
1693
            // owning side MUST have a join table
1694 149
            if ( ! isset($mapping['joinTable']['name'])) {
1695 28
                $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
1696
            }
1697
1698 149
            $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
1699 149
                && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
1700
1701 149 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 149 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 149
            $mapping['joinTableColumns'] = [];
1722
1723 149 View Code Duplication
            foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
1724 149
                if (empty($joinColumn['name'])) {
1725 2
                    $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
1726
                }
1727
1728 149
                if (empty($joinColumn['referencedColumnName'])) {
1729 6
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1730
                }
1731
1732 149
                if ($joinColumn['name'][0] === '`') {
1733 3
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1734 3
                    $joinColumn['quoted'] = true;
1735
                }
1736
1737 149
                if ($joinColumn['referencedColumnName'][0] === '`') {
1738 3
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1739 3
                    $joinColumn['quoted']               = true;
1740
                }
1741
1742 149
                if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') {
1743 37
                    $mapping['isOnDeleteCascade'] = true;
1744
                }
1745
1746 149
                $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1747 149
                $mapping['joinTableColumns'][] = $joinColumn['name'];
1748
            }
1749
1750 149 View Code Duplication
            foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) {
1751 149
                if (empty($inverseJoinColumn['name'])) {
1752 2
                    $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']);
1753
                }
1754
1755 149
                if (empty($inverseJoinColumn['referencedColumnName'])) {
1756 6
                    $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1757
                }
1758
1759 149
                if ($inverseJoinColumn['name'][0] === '`') {
1760 3
                    $inverseJoinColumn['name']   = trim($inverseJoinColumn['name'], '`');
1761 3
                    $inverseJoinColumn['quoted'] = true;
1762
                }
1763
1764 149
                if ($inverseJoinColumn['referencedColumnName'][0] === '`') {
1765 3
                    $inverseJoinColumn['referencedColumnName']  = trim($inverseJoinColumn['referencedColumnName'], '`');
1766 3
                    $inverseJoinColumn['quoted']                = true;
1767
                }
1768
1769 149
                if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
1770 33
                    $mapping['isOnDeleteCascade'] = true;
1771
                }
1772
1773 149
                $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
1774 149
                $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
1775
            }
1776
        }
1777
1778 167
        $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1779
1780 167
        $this->assertMappingOrderBy($mapping);
1781
1782 167
        return $mapping;
1783
    }
1784
1785
    /**
1786
     * {@inheritDoc}
1787
     */
1788 626
    public function getIdentifierFieldNames()
1789
    {
1790 626
        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 1168
    public function getSingleIdentifierFieldName()
1802
    {
1803 1168
        if ($this->isIdentifierComposite) {
1804 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
1805
        }
1806
1807 1167
        if ( ! isset($this->identifier[0])) {
1808 1
            throw MappingException::noIdDefined($this->name);
1809
        }
1810
1811 1166
        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 139
    public function setIdentifier(array $identifier)
1837
    {
1838 139
        $this->identifier = $identifier;
1839 139
        $this->isIdentifierComposite = (count($this->identifier) > 1);
1840 139
    }
1841
1842
    /**
1843
     * {@inheritDoc}
1844
     */
1845 65
    public function getIdentifier()
1846
    {
1847 65
        return $this->identifier;
1848
    }
1849
1850
    /**
1851
     * {@inheritDoc}
1852
     */
1853 309
    public function hasField($fieldName)
1854
    {
1855 309
        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 493
    public function setIdGeneratorType($generatorType)
1908
    {
1909 493
        $this->generatorType = $generatorType;
1910 493
    }
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 436
    public function usesIdGenerator()
1918
    {
1919 436
        return $this->generatorType != self::GENERATOR_TYPE_NONE;
1920
    }
1921
1922
    /**
1923
     * @return boolean
1924
     */
1925 1386
    public function isInheritanceTypeNone()
1926
    {
1927 1386
        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 1074
    public function isInheritanceTypeJoined()
1937
    {
1938 1074
        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 1271
    public function isInheritanceTypeSingleTable()
1948
    {
1949 1271
        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 273
    public function isInheritanceTypeTablePerClass()
1959
    {
1960 273
        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 1092
    public function isIdGeneratorIdentity()
1969
    {
1970 1092
        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 337
    public function isIdGeneratorSequence()
1979
    {
1980 337
        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 93
    public function isIdGeneratorTable()
1989
    {
1990 93
        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 941
    public function getTypeOfField($fieldName)
2024
    {
2025 941
        return isset($this->fieldMappings[$fieldName])
2026 941
            ? $this->fieldMappings[$fieldName]['type']
2027 941
            : 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 1378
    public function getTableName()
2051
    {
2052 1378
        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 453
    public function setParentClasses(array $classNames)
2100
    {
2101 453
        $this->parentClasses = $classNames;
2102
2103 453
        if (count($classNames) > 0) {
2104 92
            $this->rootEntityName = array_pop($classNames);
2105
        }
2106 453
    }
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 189
    public function setInheritanceType($type)
2118
    {
2119 189
        if ( ! $this->_isInheritanceType($type)) {
2120
            throw MappingException::invalidInheritanceType($this->name, $type);
2121
        }
2122
2123 189
        $this->inheritanceType = $type;
2124 189
    }
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 411
    public function isInheritedField($fieldName)
2235
    {
2236 411
        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 452
    public function isRootEntity()
2245
    {
2246 452
        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 388
    public function isInheritedAssociation($fieldName)
2257
    {
2258 388
        return isset($this->associationMappings[$fieldName]['inherited']);
2259
    }
2260
2261 388
    public function isInheritedEmbeddedClass($fieldName)
2262
    {
2263 388
        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 349
    public function setPrimaryTable(array $table)
2295
    {
2296 349
        if (isset($table['name'])) {
2297
            // Split schema and table name from a table name like "myschema.mytable"
2298 283
            if (strpos($table['name'], '.') !== false) {
2299 9
                list($this->table['schema'], $table['name']) = explode('.', $table['name'], 2);
2300
            }
2301
2302 283
            if ($table['name'][0] === '`') {
2303 17
                $table['name']          = trim($table['name'], '`');
2304 17
                $this->table['quoted']  = true;
2305
            }
2306
2307 283
            $this->table['name'] = $table['name'];
2308
        }
2309
2310 349
        if (isset($table['quoted'])) {
2311 2
            $this->table['quoted'] = $table['quoted'];
2312
        }
2313
2314 349
        if (isset($table['schema'])) {
2315 6
            $this->table['schema'] = $table['schema'];
2316
        }
2317
2318 349
        if (isset($table['indexes'])) {
2319 18
            $this->table['indexes'] = $table['indexes'];
2320
        }
2321
2322 349
        if (isset($table['uniqueConstraints'])) {
2323 9
            $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
2324
        }
2325
2326 349
        if (isset($table['options'])) {
2327 11
            $this->table['options'] = $table['options'];
2328
        }
2329 349
    }
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 189
    private function _isInheritanceType($type)
2339
    {
2340 189
        return $type == self::INHERITANCE_TYPE_NONE ||
2341 116
                $type == self::INHERITANCE_TYPE_SINGLE_TABLE ||
2342 55
                $type == self::INHERITANCE_TYPE_JOINED ||
2343 189
                $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 580
    public function mapField(array $mapping)
2356
    {
2357 580
        $this->_validateAndCompleteFieldMapping($mapping);
2358 578
        $this->assertFieldNotMapped($mapping['fieldName']);
2359
2360 577
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2361 577
    }
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 123
    public function addInheritedFieldMapping(array $fieldMapping)
2392
    {
2393 123
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
2394 123
        $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];
2395 123
        $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
2396 123
    }
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 184
    public function mapOneToOne(array $mapping)
2546
    {
2547 184
        $mapping['type'] = self::ONE_TO_ONE;
2548
2549 184
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2550
2551 181
        $this->_storeAssociationMapping($mapping);
2552 180
    }
2553
2554
    /**
2555
     * Adds a one-to-many mapping.
2556
     *
2557
     * @param array $mapping The mapping.
2558
     *
2559
     * @return void
2560
     */
2561 142
    public function mapOneToMany(array $mapping)
2562
    {
2563 142
        $mapping['type'] = self::ONE_TO_MANY;
2564
2565 142
        $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2566
2567 141
        $this->_storeAssociationMapping($mapping);
2568 141
    }
2569
2570
    /**
2571
     * Adds a many-to-one mapping.
2572
     *
2573
     * @param array $mapping The mapping.
2574
     *
2575
     * @return void
2576
     */
2577 168
    public function mapManyToOne(array $mapping)
2578
    {
2579 168
        $mapping['type'] = self::MANY_TO_ONE;
2580
2581
        // A many-to-one mapping is essentially a one-one backreference
2582 168
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2583
2584 163
        $this->_storeAssociationMapping($mapping);
2585 163
    }
2586
2587
    /**
2588
     * Adds a many-to-many mapping.
2589
     *
2590
     * @param array $mapping The mapping.
2591
     *
2592
     * @return void
2593
     */
2594 169
    public function mapManyToMany(array $mapping)
2595
    {
2596 169
        $mapping['type'] = self::MANY_TO_MANY;
2597
2598 169
        $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2599
2600 167
        $this->_storeAssociationMapping($mapping);
2601 167
    }
2602
2603
    /**
2604
     * Stores the association mapping.
2605
     *
2606
     * @param array $assocMapping
2607
     *
2608
     * @return void
2609
     *
2610
     * @throws MappingException
2611
     */
2612 366
    protected function _storeAssociationMapping(array $assocMapping)
2613
    {
2614 366
        $sourceFieldName = $assocMapping['fieldName'];
2615
2616 366
        $this->assertFieldNotMapped($sourceFieldName);
2617
2618 365
        $this->associationMappings[$sourceFieldName] = $assocMapping;
2619 365
    }
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 138
    public function setLifecycleCallbacks(array $callbacks)
2701
    {
2702 138
        $this->lifecycleCallbacks = $callbacks;
2703 138
    }
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 181
    public function setDiscriminatorColumn($columnDef)
2750
    {
2751 181
        if ($columnDef !== null) {
2752 120
            if ( ! isset($columnDef['name'])) {
2753 1
                throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name);
2754
            }
2755
2756 119
            if (isset($this->fieldNames[$columnDef['name']])) {
2757 1
                throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
2758
            }
2759
2760 118
            if ( ! isset($columnDef['fieldName'])) {
2761 113
                $columnDef['fieldName'] = $columnDef['name'];
2762
            }
2763
2764 118
            if ( ! isset($columnDef['type'])) {
2765 2
                $columnDef['type'] = "string";
2766
            }
2767
2768 118
            if (in_array($columnDef['type'], ["boolean", "array", "object", "datetime", "time", "date"])) {
2769
                throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
2770
            }
2771
2772 118
            $this->discriminatorColumn = $columnDef;
2773
        }
2774 179
    }
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 173
    public function setDiscriminatorMap(array $map)
2785
    {
2786 173
        foreach ($map as $value => $className) {
2787 113
            $this->addDiscriminatorMapClass($value, $className);
2788
        }
2789 173
    }
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 114
    public function addDiscriminatorMapClass($name, $className)
2802
    {
2803 114
        $className = $this->fullyQualifiedClassName($className);
2804 114
        $className = ltrim($className, '\\');
2805
2806 114
        $this->discriminatorMap[$name] = $className;
2807
2808 114
        if ($this->name === $className) {
2809 85
            $this->discriminatorValue = $name;
2810
2811 85
            return;
2812
        }
2813
2814 113
        if ( ! (class_exists($className) || interface_exists($className))) {
2815
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
2816
        }
2817
2818 113
        if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) {
2819 104
            $this->subClasses[] = $className;
2820
        }
2821 113
    }
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 349
    public function hasAssociation($fieldName)
2863
    {
2864 349
        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 1050
    public function isCollectionValuedAssociation($fieldName)
2880
    {
2881 1050
        return isset($this->associationMappings[$fieldName])
2882 1050
            && ! ($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 670
    public function getFieldForColumn($columnName)
2947
    {
2948 670
        if (isset($this->fieldNames[$columnName])) {
2949 647
            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 455
    public function setIdGenerator($generator)
2971
    {
2972 455
        $this->idGenerator = $generator;
2973 455
    }
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 138
    public function setVersioned($bool)
3054
    {
3055 138
        $this->isVersioned = $bool;
3056 138
    }
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 138
    public function setVersionField($versionField)
3067
    {
3068 138
        $this->versionField = $versionField;
3069 138
    }
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 766
    public function getName()
3115
    {
3116 766
        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 511
    public function fullyQualifiedClassName($className)
3249
    {
3250 511
        if (empty($className)) {
3251 49
            return $className;
3252
        }
3253
3254 495
        if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
3255 379
            return $this->namespace . '\\' . $className;
3256
        }
3257
3258 250
        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 616
    private function assertFieldNotMapped($fieldName)
3337
    {
3338 616
        if (isset($this->fieldMappings[$fieldName]) ||
3339 616
            isset($this->associationMappings[$fieldName]) ||
3340 616
            isset($this->embeddedClasses[$fieldName])) {
3341
3342 2
            throw MappingException::duplicateFieldMapping($this->name, $fieldName);
3343
        }
3344 616
    }
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 232
    private function assertMappingOrderBy(array $mapping)
3392
    {
3393 232
        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 232
    }
3397
}
3398