Completed
Pull Request — master (#5883)
by Sebastian
19:21
created

ClassMetadataInfo::getSqlResultSetMappings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

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