Completed
Push — master ( b59b96...3dfc18 )
by Marco
14:55
created

ClassMetadataInfo::setCustomRepositoryClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 3
cts 3
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Mapping;
21
22
use BadMethodCallException;
23
use Doctrine\Instantiator\Instantiator;
24
use InvalidArgumentException;
25
use RuntimeException;
26
use Doctrine\DBAL\Types\Type;
27
use Doctrine\DBAL\Platforms\AbstractPlatform;
28
use ReflectionClass;
29
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Doctrine\ORM\Mapping\ClassMetadata.

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
30
use Doctrine\Common\ClassLoader;
31
use Doctrine\ORM\Cache\CacheException;
32
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 = array();
279
280
    /**
281
     * READ-ONLY: The names of all subclasses (descendants).
282
     *
283
     * @var array
284
     */
285
    public $subClasses = array();
286
287
    /**
288
     * READ-ONLY: The names of all embedded classes based on properties.
289
     *
290
     * @var array
291
     */
292
    public $embeddedClasses = array();
293
294
    /**
295
     * READ-ONLY: The named queries allowed to be called directly from Repository.
296
     *
297
     * @var array
298
     */
299
    public $namedQueries = array();
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 = array();
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 = array();
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 = array();
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 = array();
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 = array();
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 = array();
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 = array();
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 = array();
469
470
    /**
471
     * READ-ONLY: The registered entity listeners.
472
     *
473
     * @var array
474
     */
475
    public $entityListeners = array();
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 = array();
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 = array();
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 647
    public function __construct($entityName, NamingStrategy $namingStrategy = null)
660
    {
661 647
        $this->name = $entityName;
662 647
        $this->rootEntityName = $entityName;
663 647
        $this->namingStrategy = $namingStrategy ?: new DefaultNamingStrategy();
664 647
        $this->instantiator   = new Instantiator();
665 647
    }
666
667
    /**
668
     * Gets the ReflectionProperties of the mapped class.
669
     *
670
     * @return array An array of ReflectionProperty instances.
671
     */
672 224
    public function getReflectionProperties()
673
    {
674 224
        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 465
    public function getIdentifierValues($entity)
716
    {
717 465
        if ($this->isIdentifierComposite) {
718 90
            $id = array();
719
720 90
            foreach ($this->identifier as $idField) {
721 90
                $value = $this->reflFields[$idField]->getValue($entity);
722
723 90
                if ($value !== null) {
724 90
                    $id[$idField] = $value;
725
                }
726
            }
727
728 90
            return $id;
729
        }
730
731 446
        $id = $this->identifier[0];
732 446
        $value = $this->reflFields[$id]->getValue($entity);
733
734 446
        if (null === $value) {
735 28
            return array();
736
        }
737
738 423
        return array($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 6
    public function setIdentifierValues($entity, array $id)
752
    {
753 6
        foreach ($id as $idField => $idValue) {
754 6
            $this->reflFields[$idField]->setValue($entity, $idValue);
755
        }
756 6
    }
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 231
    public function setFieldValue($entity, $field, $value)
768
    {
769 231
        $this->reflFields[$field]->setValue($entity, $value);
770 231
    }
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 306
    public function getFieldValue($entity, $field)
781
    {
782 306
        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 = array(
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->lifecycleCallbacks of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
871
            $serialized[] = 'lifecycleCallbacks';
872
        }
873
874 6
        if ($this->entityListeners) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->entityListeners of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
875 1
            $serialized[] = 'entityListeners';
876
        }
877
878 6
        if ($this->namedQueries) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->namedQueries of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
879 1
            $serialized[] = 'namedQueries';
880
        }
881
882 6
        if ($this->namedNativeQueries) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->namedNativeQueries of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
883
            $serialized[] = 'namedNativeQueries';
884
        }
885
886 6
        if ($this->sqlResultSetMappings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sqlResultSetMappings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
887
            $serialized[] = 'sqlResultSetMappings';
888
        }
889
890 6
        if ($this->isReadOnly) {
891 1
            $serialized[] = 'isReadOnly';
892
        }
893
894 6
        if ($this->customGeneratorDefinition) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customGeneratorDefinition of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
895
            $serialized[] = "customGeneratorDefinition";
896
        }
897
898 6
        if ($this->cache) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cache of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
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 672
    public function newInstance()
911
    {
912 672
        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 2007
    public function wakeupReflection($reflService)
923
    {
924
        // Restore ReflectionClass and properties
925 2007
        $this->reflClass    = $reflService->getClass($this->name);
926 2007
        $this->instantiator = $this->instantiator ?: new Instantiator();
927
928 2007
        $parentReflFields = array();
929
930 2007
        foreach ($this->embeddedClasses as $property => $embeddedClass) {
931 21
            if (isset($embeddedClass['declaredField'])) {
932 15
                $parentReflFields[$property] = new ReflectionEmbeddedProperty(
933 15
                    $parentReflFields[$embeddedClass['declaredField']],
934 15
                    $reflService->getAccessibleProperty(
0 ignored issues
show
Bug introduced by
It seems like $reflService->getAccessi...Class['originalField']) can be null; however, __construct() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
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 21
            $fieldRefl = $reflService->getAccessibleProperty(
945 21
                isset($embeddedClass['declared']) ? $embeddedClass['declared'] : $this->name,
946
                $property
947
            );
948
949 21
            $parentReflFields[$property] = $fieldRefl;
950 21
            $this->reflFields[$property] = $fieldRefl;
951
        }
952
953 2007
        foreach ($this->fieldMappings as $field => $mapping) {
954 2002
            if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
955 20
                $this->reflFields[$field] = new ReflectionEmbeddedProperty(
956 20
                    $parentReflFields[$mapping['declaredField']],
957 20
                    $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
0 ignored issues
show
Bug introduced by
It seems like $reflService->getAccessi...pping['originalField']) can be null; however, __construct() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
958 20
                    $mapping['originalClass']
959
                );
960 20
                continue;
961
            }
962
963 2002
            $this->reflFields[$field] = isset($mapping['declared'])
964 496
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
965 2002
                : $reflService->getAccessibleProperty($this->name, $field);
966
        }
967
968 2007
        foreach ($this->associationMappings as $field => $mapping) {
969 1694
            $this->reflFields[$field] = isset($mapping['declared'])
970 403
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
971 1694
                : $reflService->getAccessibleProperty($this->name, $field);
972
        }
973 2007
    }
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 611
    public function initializeReflection($reflService)
984
    {
985 611
        $this->reflClass = $reflService->getClass($this->name);
986 611
        $this->namespace = $reflService->getClassNamespace($this->name);
987
988 611
        if ($this->reflClass) {
989 604
            $this->name = $this->rootEntityName = $this->reflClass->getName();
0 ignored issues
show
Bug introduced by
Consider using $this->reflClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
990
        }
991
992 611
        $this->table['name'] = $this->namingStrategy->classToTableName($this->name);
993 611
    }
994
995
    /**
996
     * Validates Identifier.
997
     *
998
     * @return void
999
     *
1000
     * @throws MappingException
1001
     */
1002 400
    public function validateIdentifier()
1003
    {
1004 400
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
1005 60
            return;
1006
        }
1007
1008
        // Verify & complete identifier mapping
1009 398
        if ( ! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1010 6
            throw MappingException::identifierRequired($this->name);
1011
        }
1012
1013 392
        if ($this->usesIdGenerator() && $this->isIdentifierComposite) {
1014
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name);
1015
        }
1016 392
    }
1017
1018
    /**
1019
     * Validates association targets actually exist.
1020
     *
1021
     * @return void
1022
     *
1023
     * @throws MappingException
1024
     */
1025 401
    public function validateAssociations()
1026
    {
1027 401
        foreach ($this->associationMappings as $mapping) {
1028 267
            if ( ! ClassLoader::classExists($mapping['targetEntity']) ) {
1029 267
                throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']);
1030
            }
1031
        }
1032 400
    }
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 401
    public function validateLifecycleCallbacks($reflService)
1044
    {
1045 401
        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 400
    }
1053
1054
    /**
1055
     * {@inheritDoc}
1056
     */
1057 529
    public function getReflectionClass()
1058
    {
1059 529
        return $this->reflClass;
1060
    }
1061
1062
    /**
1063
     * @param array $cache
1064
     *
1065
     * @return void
1066
     */
1067 21
    public function enableCache(array $cache)
1068
    {
1069 21
        if ( ! isset($cache['usage'])) {
1070
            $cache['usage'] = self::CACHE_USAGE_READ_ONLY;
1071
        }
1072
1073 21
        if ( ! isset($cache['region'])) {
1074 21
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName));
1075
        }
1076
1077 21
        $this->cache = $cache;
1078 21
    }
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 17
    public function getAssociationCacheDefaults($fieldName, array $cache)
1098
    {
1099 17
        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 17
        if ( ! isset($cache['region'])) {
1106 17
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
1107
        }
1108
1109 17
        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 139
    public function setChangeTrackingPolicy($policy)
1120
    {
1121 139
        $this->changeTrackingPolicy = $policy;
1122 139
    }
1123
1124
    /**
1125
     * Whether the change tracking policy of this class is "deferred explicit".
1126
     *
1127
     * @return boolean
1128
     */
1129 267
    public function isChangeTrackingDeferredExplicit()
1130
    {
1131 267
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_EXPLICIT;
1132
    }
1133
1134
    /**
1135
     * Whether the change tracking policy of this class is "deferred implicit".
1136
     *
1137
     * @return boolean
1138
     */
1139 459
    public function isChangeTrackingDeferredImplicit()
1140
    {
1141 459
        return $this->changeTrackingPolicy == self::CHANGETRACKING_DEFERRED_IMPLICIT;
1142
    }
1143
1144
    /**
1145
     * Whether the change tracking policy of this class is "notify".
1146
     *
1147
     * @return boolean
1148
     */
1149 290
    public function isChangeTrackingNotify()
1150
    {
1151 290
        return $this->changeTrackingPolicy == self::CHANGETRACKING_NOTIFY;
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 1070
    public function isIdentifier($fieldName)
1163
    {
1164 1070
        if ( ! $this->identifier) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identifier of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1165 1
            return false;
1166
        }
1167
1168 1069
        if ( ! $this->isIdentifierComposite) {
1169 1064
            return $fieldName === $this->identifier[0];
1170
        }
1171
1172 93
        return in_array($fieldName, $this->identifier);
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
        if ($mapping !== false) {
1187
            return isset($mapping['unique']) && $mapping['unique'] == true;
1188
        }
1189
1190
        return false;
1191
    }
1192
1193
    /**
1194
     * Checks if the field is not null.
1195
     *
1196
     * @param string $fieldName The field name.
1197
     *
1198
     * @return boolean TRUE if the field is not null, FALSE otherwise.
1199
     */
1200 1
    public function isNullable($fieldName)
1201
    {
1202 1
        $mapping = $this->getFieldMapping($fieldName);
1203
1204 1
        if ($mapping !== false) {
1205 1
            return isset($mapping['nullable']) && $mapping['nullable'] == true;
1206
        }
1207
1208
        return false;
1209
    }
1210
1211
    /**
1212
     * Gets a column name for a field name.
1213
     * If the column name for the field cannot be found, the given field name
1214
     * is returned.
1215
     *
1216
     * @param string $fieldName The field name.
1217
     *
1218
     * @return string The column name.
1219
     */
1220 16
    public function getColumnName($fieldName)
1221
    {
1222 16
        return isset($this->columnNames[$fieldName])
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\Cla...adataInfo::$columnNames has been deprecated with message: 3.0 Remove this.

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

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

Loading history...
1223 16
            ? $this->columnNames[$fieldName]
0 ignored issues
show
Deprecated Code introduced by
The property Doctrine\ORM\Mapping\Cla...adataInfo::$columnNames has been deprecated with message: 3.0 Remove this.

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

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

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

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

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

Loading history...
1414
1415 527
        if (isset($this->fieldNames[$mapping['columnName']]) || ($this->discriminatorColumn != null && $this->discriminatorColumn['name'] == $mapping['columnName'])) {
1416 2
            throw MappingException::duplicateColumnName($this->name, $mapping['columnName']);
1417
        }
1418
1419 526
        $this->fieldNames[$mapping['columnName']] = $mapping['fieldName'];
1420
1421
        // Complete id mapping
1422 526
        if (isset($mapping['id']) && $mapping['id'] === true) {
1423 488
            if ($this->versionField == $mapping['fieldName']) {
1424
                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
1425
            }
1426
1427 488
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1428 488
                $this->identifier[] = $mapping['fieldName'];
1429
            }
1430
1431
            // Check for composite key
1432 488
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1433 17
                $this->isIdentifierComposite = true;
1434
            }
1435
        }
1436
1437 526
        if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
1438 5
            if (isset($mapping['id']) && $mapping['id'] === true) {
1439
                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
1440
            }
1441
1442 5
            $mapping['requireSQLConversion'] = true;
1443
        }
1444 526
    }
1445
1446
    /**
1447
     * Validates & completes the basic mapping information that is common to all
1448
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
1449
     *
1450
     * @param array $mapping The mapping.
1451
     *
1452
     * @return array The updated mapping.
1453
     *
1454
     * @throws MappingException If something is wrong with the mapping.
1455
     */
1456 350
    protected function _validateAndCompleteAssociationMapping(array $mapping)
1457
    {
1458 350
        if ( ! isset($mapping['mappedBy'])) {
1459 336
            $mapping['mappedBy'] = null;
1460
        }
1461
1462 350
        if ( ! isset($mapping['inversedBy'])) {
1463 323
            $mapping['inversedBy'] = null;
1464
        }
1465
1466 350
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
1467
1468
        // unset optional indexBy attribute if its empty
1469 350
        if ( ! isset($mapping['indexBy']) || !$mapping['indexBy']) {
1470 347
            unset($mapping['indexBy']);
1471
        }
1472
1473
        // If targetEntity is unqualified, assume it is in the same namespace as
1474
        // the sourceEntity.
1475 350
        $mapping['sourceEntity'] = $this->name;
1476
1477 350
        if (isset($mapping['targetEntity'])) {
1478 350
            $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
1479 350
            $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
1480
        }
1481
1482 350
        if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) {
1483 1
            throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
1484
        }
1485
1486
        // Complete id mapping
1487 349
        if (isset($mapping['id']) && $mapping['id'] === true) {
1488 53
            if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'] == true) {
1489 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
1490
            }
1491
1492 52
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1493 52
                if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) {
1494
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
1495
                        $mapping['targetEntity'], $this->name, $mapping['fieldName']
1496
                    );
1497
                }
1498
1499 52
                $this->identifier[] = $mapping['fieldName'];
1500 52
                $this->containsForeignIdentifier = true;
1501
            }
1502
1503
            // Check for composite key
1504 52
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1505 24
                $this->isIdentifierComposite = true;
1506
            }
1507
1508 52
            if ($this->cache && !isset($mapping['cache'])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cache of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1509 3
                throw CacheException::nonCacheableEntityAssociation($this->name, $mapping['fieldName']);
1510
            }
1511
        }
1512
1513
        // Mandatory attributes for both sides
1514
        // Mandatory: fieldName, targetEntity
1515 345
        if ( ! isset($mapping['fieldName']) || strlen($mapping['fieldName']) == 0) {
1516
            throw MappingException::missingFieldName($this->name);
1517
        }
1518
1519 345
        if ( ! isset($mapping['targetEntity'])) {
1520
            throw MappingException::missingTargetEntity($mapping['fieldName']);
1521
        }
1522
1523
        // Mandatory and optional attributes for either side
1524 345
        if ( ! $mapping['mappedBy']) {
1525 331
            if (isset($mapping['joinTable']) && $mapping['joinTable']) {
1526 119
                if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
1527 4
                    $mapping['joinTable']['name']   = trim($mapping['joinTable']['name'], '`');
1528 331
                    $mapping['joinTable']['quoted'] = true;
1529
                }
1530
            }
1531
        } else {
1532 182
            $mapping['isOwningSide'] = false;
1533
        }
1534
1535 345
        if (isset($mapping['id']) && $mapping['id'] === true && $mapping['type'] & self::TO_MANY) {
1536 3
            throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']);
1537
        }
1538
1539
        // Fetch mode. Default fetch mode to LAZY, if not set.
1540 342
        if ( ! isset($mapping['fetch'])) {
1541 95
            $mapping['fetch'] = self::FETCH_LAZY;
1542
        }
1543
1544
        // Cascades
1545 342
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : array();
1546
1547 342
        if (in_array('all', $cascades)) {
1548 36
            $cascades = array('remove', 'persist', 'refresh', 'merge', 'detach');
1549
        }
1550
1551 342
        if (count($cascades) !== count(array_intersect($cascades, array('remove', 'persist', 'refresh', 'merge', 'detach')))) {
1552 1
            throw MappingException::invalidCascadeOption(
1553 1
                array_diff($cascades, array_intersect($cascades, array('remove', 'persist', 'refresh', 'merge', 'detach'))),
1554 1
                $this->name,
1555 1
                $mapping['fieldName']
1556
            );
1557
        }
1558
1559 341
        $mapping['cascade'] = $cascades;
1560 341
        $mapping['isCascadeRemove'] = in_array('remove', $cascades);
1561 341
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1562 341
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1563 341
        $mapping['isCascadeMerge'] = in_array('merge', $cascades);
1564 341
        $mapping['isCascadeDetach'] = in_array('detach', $cascades);
1565
1566 341
        return $mapping;
1567
    }
1568
1569
    /**
1570
     * Validates & completes a one-to-one association mapping.
1571
     *
1572
     * @param array $mapping The mapping to validate & complete.
1573
     *
1574
     * @return array The validated & completed mapping.
1575
     *
1576
     * @throws RuntimeException
1577
     * @throws MappingException
1578
     */
1579 297
    protected function _validateAndCompleteOneToOneMapping(array $mapping)
1580
    {
1581 297
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1582
1583 291
        if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
1584 210
            $mapping['isOwningSide'] = true;
1585
        }
1586
1587 291
        if ($mapping['isOwningSide']) {
1588 278
            if ( ! isset($mapping['joinColumns']) || ! $mapping['joinColumns']) {
1589
                // Apply default join column
1590 89
                $mapping['joinColumns'] = array(
1591
                    array(
1592 89
                        'name' => $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name),
0 ignored issues
show
Unused Code introduced by
The call to NamingStrategy::joinColumnName() has too many arguments starting with $this->name.

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

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

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

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

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

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

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

Loading history...
1613
                }
1614
1615 278
                if (empty($joinColumn['referencedColumnName'])) {
1616 5
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1617
                }
1618
1619 278
                if ($joinColumn['name'][0] === '`') {
1620 7
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1621 7
                    $joinColumn['quoted'] = true;
1622
                }
1623
1624 278
                if ($joinColumn['referencedColumnName'][0] === '`') {
1625 4
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1626 4
                    $joinColumn['quoted']               = true;
1627
                }
1628
1629 278
                $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1630 278
                $mapping['joinColumnFieldNames'][$joinColumn['name']] = isset($joinColumn['fieldName'])
1631
                    ? $joinColumn['fieldName']
1632 278
                    : $joinColumn['name'];
1633
            }
1634
1635 278
            if ($uniqueConstraintColumns) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uniqueConstraintColumns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

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

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

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

Loading history...
1637
                    throw new RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.");
1638
                }
1639
1640 2
                $this->table['uniqueConstraints'][$mapping['fieldName'] . "_uniq"] = array(
1641 2
                    'columns' => $uniqueConstraintColumns
1642
                );
1643
            }
1644
1645 278
            $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
1646
        }
1647
1648 291
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
1649 291
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
1650
1651 291
        if ($mapping['orphanRemoval']) {
1652 21
            unset($mapping['unique']);
1653
        }
1654
1655 291
        if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
1656 2
            throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
1657
        }
1658
1659 289
        return $mapping;
1660
    }
1661
1662
    /**
1663
     * Validates & completes a one-to-many association mapping.
1664
     *
1665
     * @param array $mapping The mapping to validate and complete.
1666
     *
1667
     * @return array The validated and completed mapping.
1668
     *
1669
     * @throws MappingException
1670
     * @throws InvalidArgumentException
1671
     */
1672 128
    protected function _validateAndCompleteOneToManyMapping(array $mapping)
1673
    {
1674 128
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1675
1676
        // OneToMany-side MUST be inverse (must have mappedBy)
1677 127
        if ( ! isset($mapping['mappedBy'])) {
1678
            throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
1679
        }
1680
1681 127
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) ? (bool) $mapping['orphanRemoval'] : false;
1682 127
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] ? true : $mapping['isCascadeRemove'];
1683
1684 127
        if (isset($mapping['orderBy'])) {
1685 37
            if ( ! is_array($mapping['orderBy'])) {
1686
                throw new InvalidArgumentException("'orderBy' is expected to be an array, not ".gettype($mapping['orderBy']));
1687
            }
1688
        }
1689
1690 127
        return $mapping;
1691
    }
1692
1693
    /**
1694
     * Validates & completes a many-to-many association mapping.
1695
     *
1696
     * @param array $mapping The mapping to validate & complete.
1697
     *
1698
     * @return array The validated & completed mapping.
1699
     *
1700
     * @throws \InvalidArgumentException
1701
     */
1702 149
    protected function _validateAndCompleteManyToManyMapping(array $mapping)
1703
    {
1704 149
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1705
1706 147
        if ($mapping['isOwningSide']) {
1707
            // owning side MUST have a join table
1708 129
            if ( ! isset($mapping['joinTable']['name'])) {
1709 22
                $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
1710
            }
1711
1712 129
            $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
1713 129
                && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
1714
1715 129
            if ( ! isset($mapping['joinTable']['joinColumns'])) {
1716 21
                $mapping['joinTable']['joinColumns'] = array(
1717
                    array(
1718 21
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
1719 21
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1720 21
                        'onDelete' => 'CASCADE'
1721
                    )
1722
                );
1723
            }
1724
1725 129
            if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
1726 22
                $mapping['joinTable']['inverseJoinColumns'] = array(
1727
                    array(
1728 22
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
1729 22
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1730 22
                        'onDelete' => 'CASCADE'
1731
                    )
1732
                );
1733
            }
1734
1735 129
            $mapping['joinTableColumns'] = array();
1736
1737 129
            foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
1738 129
                if (empty($joinColumn['name'])) {
1739 2
                    $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
0 ignored issues
show
Documentation introduced by
$mapping['sourceEntity'] is of type array|boolean, but the function expects a string.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

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

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

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

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

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

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

    return array();
}

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

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

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

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

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

    return array();
}

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

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

Loading history...
2518
2519 38
                $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
2520 38
                $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
2521
2522 38
                if (isset($entityResult['fields'])) {
2523 34
                    foreach ($entityResult['fields'] as $k => $field) {
0 ignored issues
show
Bug introduced by
The expression $entityResult['fields'] of type boolean|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

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