Completed
Pull Request — master (#5962)
by Pavel
11:40
created

ClassMetadataInfo::setAssociationOverride()   D

Complexity

Conditions 9
Paths 41

Size

Total Lines 43
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 9.0239

Importance

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