Completed
Pull Request — master (#6284)
by Luís
10:31
created

ClassMetadataInfo   F

Complexity

Total Complexity 420

Size/Duplication

Total Lines 3338
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 89.57%

Importance

Changes 0
Metric Value
wmc 420
lcom 1
cbo 11
dl 0
loc 3338
ccs 807
cts 901
cp 0.8957
rs 3.9999
c 0
b 0
f 0

136 Methods

Rating   Name   Duplication   Size   Complexity  
A getColumnName() 0 6 2
A getFieldMapping() 0 8 2
A getAssociationMapping() 0 8 2
A getAssociationMappings() 0 4 1
A getFieldName() 0 6 2
A getNamedQuery() 0 8 2
A getNamedQueries() 0 4 1
A getNamedNativeQuery() 0 8 2
A getNamedNativeQueries() 0 4 1
A getSqlResultSetMapping() 0 8 2
A getSqlResultSetMappings() 0 4 1
C _validateAndCompleteFieldMapping() 0 54 19
A __construct() 0 7 2
A getReflectionProperties() 0 4 1
A getReflectionProperty() 0 4 1
A getSingleIdReflectionProperty() 0 8 2
B getIdentifierValues() 0 28 6
A setIdentifierValues() 0 6 2
A setFieldValue() 0 4 1
A getFieldValue() 0 4 1
A __toString() 0 4 1
F __sleep() 0 94 18
A newInstance() 0 4 1
C wakeupReflection() 0 52 11
A initializeReflection() 0 11 2
B validateIdentifier() 0 15 6
A validateAssociations() 0 8 3
A validateLifecycleCallbacks() 0 10 4
A getReflectionClass() 0 4 1
A enableCache() 0 12 3
A enableAssociationCache() 0 4 1
A getAssociationCacheDefaults() 0 14 4
A setChangeTrackingPolicy() 0 4 1
A isChangeTrackingDeferredExplicit() 0 4 1
A isChangeTrackingDeferredImplicit() 0 4 1
A isChangeTrackingNotify() 0 4 1
A isIdentifier() 0 12 3
A isUniqueField() 0 6 3
A isNullable() 0 6 3
F _validateAndCompleteAssociationMapping() 0 110 34
D _validateAndCompleteOneToOneMapping() 0 82 23
A _validateAndCompleteOneToManyMapping() 0 16 4
D _validateAndCompleteManyToManyMapping() 0 96 24
A getIdentifierFieldNames() 0 4 1
A getSingleIdentifierFieldName() 0 8 2
A getSingleIdentifierColumnName() 0 4 1
A setIdentifier() 0 5 1
A getIdentifier() 0 4 1
A hasField() 0 4 1
A getColumnNames() 0 8 2
A getIdentifierColumnNames() 0 20 3
A setIdGeneratorType() 0 4 1
A usesIdGenerator() 0 4 1
A isInheritanceTypeNone() 0 4 1
A isInheritanceTypeJoined() 0 4 1
A isInheritanceTypeSingleTable() 0 4 1
A isInheritanceTypeTablePerClass() 0 4 1
A isIdGeneratorIdentity() 0 4 1
A isIdGeneratorSequence() 0 4 1
A isIdGeneratorTable() 0 4 1
A isIdentifierNatural() 0 4 1
A isIdentifierUuid() 0 4 1
A getTypeOfField() 0 6 2
A getTypeOfColumn() 0 4 1
A getTableName() 0 4 1
A getSchemaName() 0 4 2
A getTemporaryIdTableName() 0 5 1
A setSubclasses() 0 6 2
A setParentClasses() 0 8 2
A setInheritanceType() 0 8 2
D setAssociationOverride() 0 43 9
B setAttributeOverride() 0 32 6
A isInheritedField() 0 4 1
A isRootEntity() 0 4 1
A isInheritedAssociation() 0 4 1
A isInheritedEmbeddedClass() 0 4 1
A setTableName() 0 4 1
C setPrimaryTable() 0 32 8
A _isInheritanceType() 0 7 4
A mapField() 0 7 1
A addInheritedAssociationMapping() 0 7 2
A addInheritedFieldMapping() 0 6 1
B addNamedQuery() 0 24 4
C addNamedNativeQuery() 0 33 8
C addSqlResultSetMapping() 0 50 12
A mapOneToOne() 0 8 1
A mapOneToMany() 0 8 1
A mapManyToOne() 0 9 1
A mapManyToMany() 0 8 1
A _storeAssociationMapping() 0 8 1
A setCustomRepositoryClass() 0 4 1
A invokeLifecycleCallbacks() 0 6 2
A hasLifecycleCallbacks() 0 4 1
A getLifecycleCallbacks() 0 4 2
A addLifecycleCallback() 0 8 3
A setLifecycleCallbacks() 0 4 1
B addEntityListener() 0 23 5
C setDiscriminatorColumn() 0 26 7
A setDiscriminatorMap() 0 6 2
B addDiscriminatorMapClass() 0 21 6
A hasNamedQuery() 0 4 1
A hasNamedNativeQuery() 0 4 1
A hasSqlResultSetMapping() 0 4 1
A hasAssociation() 0 4 1
A isSingleValuedAssociation() 0 5 2
A isCollectionValuedAssociation() 0 5 2
A isAssociationWithSingleJoinColumn() 0 6 3
A getSingleAssociationJoinColumnName() 0 8 2
A getSingleAssociationReferencedJoinColumnName() 0 8 2
B getFieldForColumn() 0 16 5
A setIdGenerator() 0 4 1
A setCustomGeneratorDefinition() 0 4 1
A setSequenceGeneratorDefinition() 0 13 3
A setVersionMapping() 0 15 4
A setVersioned() 0 4 1
A setVersionField() 0 4 1
A markReadOnly() 0 4 1
A getFieldNames() 0 4 1
A getAssociationNames() 0 4 1
A getAssociationTargetClass() 0 8 2
A getName() 0 4 1
B getQuotedIdentifierColumnNames() 0 29 5
A getQuotedColumnName() 0 6 2
A getQuotedTableName() 0 6 2
A getQuotedJoinTableName() 0 6 2
A isAssociationInverseSide() 0 5 2
A getAssociationMappedByTargetField() 0 4 1
A getAssociationsByTargetClass() 0 12 3
B fullyQualifiedClassName() 0 12 5
A getMetadataValue() 0 9 2
A mapEmbedded() 0 11 3
C inlineEmbeddable() 0 29 7
A assertFieldNotMapped() 0 9 4
A getSequenceName() 0 8 1
A getSequencePrefix() 0 16 4
A assertMappingOrderBy() 0 6 3

How to fix   Complexity   

Complex Class

Complex classes like ClassMetadataInfo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ClassMetadataInfo, and based on these observations, apply Extract Interface, too.

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

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