Passed
Pull Request — 2.8.x (#8022)
by Benjamin
08:51
created

ClassMetadataInfo   F

Complexity

Total Complexity 425

Size/Duplication

Total Lines 3357
Duplicated Lines 0 %

Test Coverage

Coverage 89.51%

Importance

Changes 2
Bugs 1 Features 0
Metric Value
wmc 425
eloc 814
c 2
b 1
f 0
dl 0
loc 3357
ccs 819
cts 915
cp 0.8951
rs 1.786

136 Methods

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

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.

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. Consider defining an alias.

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

911
        return $this->instantiator->/** @scrutinizer ignore-call */ instantiate($this->name);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
912
    }
913
914
    /**
915
     * Restores some state that can not be serialized/unserialized.
916
     *
917
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService
918
     *
919
     * @return void
920
     */
921 2153
    public function wakeupReflection($reflService)
922
    {
923
        // Restore ReflectionClass and properties
924 2153
        $this->reflClass    = $reflService->getClass($this->name);
925 2153
        $this->instantiator = $this->instantiator ?: new Instantiator();
926
927 2153
        $parentReflFields = [];
928
929 2153
        foreach ($this->embeddedClasses as $property => $embeddedClass) {
930 28
            if (isset($embeddedClass['declaredField'])) {
931 20
                $parentReflFields[$property] = new ReflectionEmbeddedProperty(
932 20
                    $parentReflFields[$embeddedClass['declaredField']],
933 20
                    $reflService->getAccessibleProperty(
0 ignored issues
show
Bug introduced by
It seems like $reflService->getAccessi...Class['originalField']) can also be of type null; however, parameter $childProperty of Doctrine\ORM\Mapping\Ref...Property::__construct() does only seem to accept ReflectionProperty, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

933
                    /** @scrutinizer ignore-type */ $reflService->getAccessibleProperty(
Loading history...
934 20
                        $this->embeddedClasses[$embeddedClass['declaredField']]['class'],
935 20
                        $embeddedClass['originalField']
936
                    ),
937 20
                    $this->embeddedClasses[$embeddedClass['declaredField']]['class'],
938 20
                    $embeddedClass['nullable']
939
                );
940
941 20
                continue;
942
            }
943
944 28
            $fieldRefl = $reflService->getAccessibleProperty(
945 28
                $embeddedClass['declared'] ?? $this->name,
946 28
                $property
947
            );
948
949 28
            $parentReflFields[$property] = $fieldRefl;
950 28
            $this->reflFields[$property] = $fieldRefl;
951
        }
952
953 2153
        foreach ($this->fieldMappings as $field => $mapping) {
954 2148
            if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
955 27
                $this->reflFields[$field] = new ReflectionEmbeddedProperty(
956 27
                    $parentReflFields[$mapping['declaredField']],
957 27
                    $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
958 27
                    $mapping['originalClass'],
959 27
                    $mapping['nullable'] ?? false
960
                );
961 27
                continue;
962
            }
963
964 2148
            $this->reflFields[$field] = isset($mapping['declared'])
965 540
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
966 2148
                : $reflService->getAccessibleProperty($this->name, $field);
967
        }
968
969 2153
        foreach ($this->associationMappings as $field => $mapping) {
970 1774
            $this->reflFields[$field] = isset($mapping['declared'])
971 425
                ? $reflService->getAccessibleProperty($mapping['declared'], $field)
972 1774
                : $reflService->getAccessibleProperty($this->name, $field);
973
        }
974 2153
    }
975
976
    /**
977
     * Initializes a new ClassMetadata instance that will hold the object-relational mapping
978
     * metadata of the class with the given name.
979
     *
980
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService The reflection service.
981
     *
982
     * @return void
983
     */
984 713
    public function initializeReflection($reflService)
985
    {
986 713
        $this->reflClass = $reflService->getClass($this->name);
987 713
        $this->namespace = $reflService->getClassNamespace($this->name);
988
989 713
        if ($this->reflClass) {
990 706
            $this->name = $this->rootEntityName = $this->reflClass->getName();
991
        }
992
993 713
        $this->table['name'] = $this->namingStrategy->classToTableName($this->name);
994 713
    }
995
996
    /**
997
     * Validates Identifier.
998
     *
999
     * @return void
1000
     *
1001
     * @throws MappingException
1002
     */
1003 486
    public function validateIdentifier()
1004
    {
1005 486
        if ($this->isMappedSuperclass || $this->isEmbeddedClass) {
1006 62
            return;
1007
        }
1008
1009
        // Verify & complete identifier mapping
1010 484
        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...
1011 6
            throw MappingException::identifierRequired($this->name);
1012
        }
1013
1014 478
        if ($this->usesIdGenerator() && $this->isIdentifierComposite) {
1015
            throw MappingException::compositeKeyAssignedIdGeneratorRequired($this->name);
1016
        }
1017 478
    }
1018
1019
    /**
1020
     * Validates association targets actually exist.
1021
     *
1022
     * @return void
1023
     *
1024
     * @throws MappingException
1025
     */
1026 487
    public function validateAssociations()
1027
    {
1028 487
        foreach ($this->associationMappings as $mapping) {
1029
            if (
1030 313
                ! class_exists($mapping['targetEntity'])
1031 313
                && ! interface_exists($mapping['targetEntity'])
1032 313
                && ! trait_exists($mapping['targetEntity'])
1033
            ) {
1034 313
                throw MappingException::invalidTargetEntityClass($mapping['targetEntity'], $this->name, $mapping['fieldName']);
1035
            }
1036
        }
1037 486
    }
1038
1039
    /**
1040
     * Validates lifecycle callbacks.
1041
     *
1042
     * @param \Doctrine\Common\Persistence\Mapping\ReflectionService $reflService
1043
     *
1044
     * @return void
1045
     *
1046
     * @throws MappingException
1047
     */
1048 487
    public function validateLifecycleCallbacks($reflService)
1049
    {
1050 487
        foreach ($this->lifecycleCallbacks as $callbacks) {
1051 13
            foreach ($callbacks as $callbackFuncName) {
1052 13
                if ( ! $reflService->hasPublicMethod($this->name, $callbackFuncName)) {
1053 13
                    throw MappingException::lifecycleCallbackMethodNotFound($this->name, $callbackFuncName);
1054
                }
1055
            }
1056
        }
1057 486
    }
1058
1059
    /**
1060
     * {@inheritDoc}
1061
     */
1062 620
    public function getReflectionClass()
1063
    {
1064 620
        return $this->reflClass;
1065
    }
1066
1067
    /**
1068
     * @param array $cache
1069
     *
1070
     * @return void
1071
     */
1072 26
    public function enableCache(array $cache)
1073
    {
1074 26
        if ( ! isset($cache['usage'])) {
1075
            $cache['usage'] = self::CACHE_USAGE_READ_ONLY;
1076
        }
1077
1078 26
        if ( ! isset($cache['region'])) {
1079 26
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName));
1080
        }
1081
1082 26
        $this->cache = $cache;
1083 26
    }
1084
1085
    /**
1086
     * @param string $fieldName
1087
     * @param array  $cache
1088
     *
1089
     * @return void
1090
     */
1091 2
    public function enableAssociationCache($fieldName, array $cache)
1092
    {
1093 2
        $this->associationMappings[$fieldName]['cache'] = $this->getAssociationCacheDefaults($fieldName, $cache);
1094 2
    }
1095
1096
    /**
1097
     * @param string $fieldName
1098
     * @param array  $cache
1099
     *
1100
     * @return array
1101
     */
1102 20
    public function getAssociationCacheDefaults($fieldName, array $cache)
1103
    {
1104 20
        if ( ! isset($cache['usage'])) {
1105 1
            $cache['usage'] = isset($this->cache['usage'])
1106 1
                ? $this->cache['usage']
1107
                : self::CACHE_USAGE_READ_ONLY;
1108
        }
1109
1110 20
        if ( ! isset($cache['region'])) {
1111 20
            $cache['region'] = strtolower(str_replace('\\', '_', $this->rootEntityName)) . '__' . $fieldName;
1112
        }
1113
1114 20
        return $cache;
1115
    }
1116
1117
    /**
1118
     * Sets the change tracking policy used by this class.
1119
     *
1120
     * @param integer $policy
1121
     *
1122
     * @return void
1123
     */
1124 167
    public function setChangeTrackingPolicy($policy)
1125
    {
1126 167
        $this->changeTrackingPolicy = $policy;
1127 167
    }
1128
1129
    /**
1130
     * Whether the change tracking policy of this class is "deferred explicit".
1131
     *
1132
     * @return boolean
1133
     */
1134 274
    public function isChangeTrackingDeferredExplicit()
1135
    {
1136 274
        return self::CHANGETRACKING_DEFERRED_EXPLICIT === $this->changeTrackingPolicy;
1137
    }
1138
1139
    /**
1140
     * Whether the change tracking policy of this class is "deferred implicit".
1141
     *
1142
     * @return boolean
1143
     */
1144 482
    public function isChangeTrackingDeferredImplicit()
1145
    {
1146 482
        return self::CHANGETRACKING_DEFERRED_IMPLICIT === $this->changeTrackingPolicy;
1147
    }
1148
1149
    /**
1150
     * Whether the change tracking policy of this class is "notify".
1151
     *
1152
     * @return boolean
1153
     */
1154 315
    public function isChangeTrackingNotify()
1155
    {
1156 315
        return self::CHANGETRACKING_NOTIFY === $this->changeTrackingPolicy;
1157
    }
1158
1159
    /**
1160
     * Checks whether a field is part of the identifier/primary key field(s).
1161
     *
1162
     * @param string $fieldName The field name.
1163
     *
1164
     * @return boolean TRUE if the field is part of the table identifier/primary key field(s),
1165
     *                 FALSE otherwise.
1166
     */
1167 1169
    public function isIdentifier($fieldName)
1168
    {
1169 1169
        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...
1170 1
            return false;
1171
        }
1172
1173 1168
        if ( ! $this->isIdentifierComposite) {
1174 1163
            return $fieldName === $this->identifier[0];
1175
        }
1176
1177 99
        return in_array($fieldName, $this->identifier, true);
1178
    }
1179
1180
    /**
1181
     * Checks if the field is unique.
1182
     *
1183
     * @param string $fieldName The field name.
1184
     *
1185
     * @return boolean TRUE if the field is unique, FALSE otherwise.
1186
     */
1187
    public function isUniqueField($fieldName)
1188
    {
1189
        $mapping = $this->getFieldMapping($fieldName);
1190
1191
        return false !== $mapping && isset($mapping['unique']) && $mapping['unique'];
1192
    }
1193
1194
    /**
1195
     * Checks if the field is not null.
1196
     *
1197
     * @param string $fieldName The field name.
1198
     *
1199
     * @return boolean TRUE if the field is not null, FALSE otherwise.
1200
     */
1201 1
    public function isNullable($fieldName)
1202
    {
1203 1
        $mapping = $this->getFieldMapping($fieldName);
1204
1205 1
        return false !== $mapping && isset($mapping['nullable']) && $mapping['nullable'];
1206
    }
1207
1208
    /**
1209
     * Gets a column name for a field name.
1210
     * If the column name for the field cannot be found, the given field name
1211
     * is returned.
1212
     *
1213
     * @param string $fieldName The field name.
1214
     *
1215
     * @return string The column name.
1216
     */
1217 16
    public function getColumnName($fieldName)
1218
    {
1219 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: 3.0 Remove this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1219
        return isset(/** @scrutinizer ignore-deprecated */ $this->columnNames[$fieldName])

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1406
            $mapping['columnName']  = trim(/** @scrutinizer ignore-type */ $mapping['columnName'], '`');
Loading history...
1407 12
            $mapping['quoted']      = true;
1408
        }
1409
1410 636
        $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: 3.0 Remove this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1410
        /** @scrutinizer ignore-deprecated */ $this->columnNames[$mapping['fieldName']] = $mapping['columnName'];

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...
1411
1412 636
        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...
1413 2
            throw MappingException::duplicateColumnName($this->name, $mapping['columnName']);
1414
        }
1415
1416 635
        $this->fieldNames[$mapping['columnName']] = $mapping['fieldName'];
1417
1418
        // Complete id mapping
1419 635
        if (isset($mapping['id']) && true === $mapping['id']) {
1420 596
            if ($this->versionField == $mapping['fieldName']) {
1421
                throw MappingException::cannotVersionIdField($this->name, $mapping['fieldName']);
1422
            }
1423
1424 596
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1425 596
                $this->identifier[] = $mapping['fieldName'];
1426
            }
1427
1428
            // Check for composite key
1429 596
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1430 24
                $this->isIdentifierComposite = true;
1431
            }
1432
        }
1433
1434 635
        if (Type::hasType($mapping['type']) && Type::getType($mapping['type'])->canRequireSQLConversion()) {
1435 5
            if (isset($mapping['id']) && true === $mapping['id']) {
1436
                 throw MappingException::sqlConversionNotAllowedForIdentifiers($this->name, $mapping['fieldName'], $mapping['type']);
1437
            }
1438
1439 5
            $mapping['requireSQLConversion'] = true;
1440
        }
1441 635
    }
1442
1443
    /**
1444
     * Validates & completes the basic mapping information that is common to all
1445
     * association mappings (one-to-one, many-ot-one, one-to-many, many-to-many).
1446
     *
1447
     * @param array $mapping The mapping.
1448
     *
1449
     * @return array The updated mapping.
1450
     *
1451
     * @throws MappingException If something is wrong with the mapping.
1452
     */
1453 406
    protected function _validateAndCompleteAssociationMapping(array $mapping)
1454
    {
1455 406
        if ( ! isset($mapping['mappedBy'])) {
1456 390
            $mapping['mappedBy'] = null;
1457
        }
1458
1459 406
        if ( ! isset($mapping['inversedBy'])) {
1460 374
            $mapping['inversedBy'] = null;
1461
        }
1462
1463 406
        $mapping['isOwningSide'] = true; // assume owning side until we hit mappedBy
1464
1465 406
        if (empty($mapping['indexBy'])) {
1466 403
            unset($mapping['indexBy']);
1467
        }
1468
1469
        // If targetEntity is unqualified, assume it is in the same namespace as
1470
        // the sourceEntity.
1471 406
        $mapping['sourceEntity'] = $this->name;
1472
1473 406
        if (isset($mapping['targetEntity'])) {
1474 406
            $mapping['targetEntity'] = $this->fullyQualifiedClassName($mapping['targetEntity']);
1475 406
            $mapping['targetEntity'] = ltrim($mapping['targetEntity'], '\\');
1476
        }
1477
1478 406
        if (($mapping['type'] & self::MANY_TO_ONE) > 0 && isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1479 1
            throw MappingException::illegalOrphanRemoval($this->name, $mapping['fieldName']);
1480
        }
1481
1482
        // Complete id mapping
1483 405
        if (isset($mapping['id']) && true === $mapping['id']) {
1484 60
            if (isset($mapping['orphanRemoval']) && $mapping['orphanRemoval']) {
1485 1
                throw MappingException::illegalOrphanRemovalOnIdentifierAssociation($this->name, $mapping['fieldName']);
1486
            }
1487
1488 59
            if ( ! in_array($mapping['fieldName'], $this->identifier)) {
1489 59
                if (isset($mapping['joinColumns']) && count($mapping['joinColumns']) >= 2) {
1490
                    throw MappingException::cannotMapCompositePrimaryKeyEntitiesAsForeignId(
1491
                        $mapping['targetEntity'], $this->name, $mapping['fieldName']
1492
                    );
1493
                }
1494
1495 59
                $this->identifier[] = $mapping['fieldName'];
1496 59
                $this->containsForeignIdentifier = true;
1497
            }
1498
1499
            // Check for composite key
1500 59
            if ( ! $this->isIdentifierComposite && count($this->identifier) > 1) {
1501 28
                $this->isIdentifierComposite = true;
1502
            }
1503
1504 59
            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...
1505 3
                throw CacheException::nonCacheableEntityAssociation($this->name, $mapping['fieldName']);
1506
            }
1507
        }
1508
1509
        // Mandatory attributes for both sides
1510
        // Mandatory: fieldName, targetEntity
1511 401
        if ( ! isset($mapping['fieldName']) || !$mapping['fieldName']) {
1512
            throw MappingException::missingFieldName($this->name);
1513
        }
1514
1515 401
        if ( ! isset($mapping['targetEntity'])) {
1516
            throw MappingException::missingTargetEntity($mapping['fieldName']);
1517
        }
1518
1519
        // Mandatory and optional attributes for either side
1520 401
        if ( ! $mapping['mappedBy']) {
1521 385
            if (isset($mapping['joinTable']) && $mapping['joinTable']) {
1522 145
                if (isset($mapping['joinTable']['name']) && $mapping['joinTable']['name'][0] === '`') {
1523 4
                    $mapping['joinTable']['name']   = trim($mapping['joinTable']['name'], '`');
1524 385
                    $mapping['joinTable']['quoted'] = true;
1525
                }
1526
            }
1527
        } else {
1528 215
            $mapping['isOwningSide'] = false;
1529
        }
1530
1531 401
        if (isset($mapping['id']) && true === $mapping['id'] && $mapping['type'] & self::TO_MANY) {
1532 3
            throw MappingException::illegalToManyIdentifierAssociation($this->name, $mapping['fieldName']);
1533
        }
1534
1535
        // Fetch mode. Default fetch mode to LAZY, if not set.
1536 398
        if ( ! isset($mapping['fetch'])) {
1537 109
            $mapping['fetch'] = self::FETCH_LAZY;
1538
        }
1539
1540
        // Cascades
1541 398
        $cascades = isset($mapping['cascade']) ? array_map('strtolower', $mapping['cascade']) : [];
1542
1543 398
        $allCascades = ['remove', 'persist', 'refresh', 'merge', 'detach'];
1544 398
        if (in_array('all', $cascades)) {
1545 46
            $cascades = $allCascades;
1546 390
        } elseif (count($cascades) !== count(array_intersect($cascades, $allCascades))) {
1547 1
            throw MappingException::invalidCascadeOption(
1548 1
                array_diff($cascades, $allCascades),
1549 1
                $this->name,
1550 1
                $mapping['fieldName']
1551
            );
1552
        }
1553
1554 397
        $mapping['cascade'] = $cascades;
1555 397
        $mapping['isCascadeRemove']  = in_array('remove', $cascades);
1556 397
        $mapping['isCascadePersist'] = in_array('persist', $cascades);
1557 397
        $mapping['isCascadeRefresh'] = in_array('refresh', $cascades);
1558 397
        $mapping['isCascadeMerge']   = in_array('merge', $cascades);
1559 397
        $mapping['isCascadeDetach']  = in_array('detach', $cascades);
1560
1561 397
        return $mapping;
1562
    }
1563
1564
    /**
1565
     * Validates & completes a one-to-one association mapping.
1566
     *
1567
     * @param array $mapping The mapping to validate & complete.
1568
     *
1569
     * @return array The validated & completed mapping.
1570
     *
1571
     * @throws RuntimeException
1572
     * @throws MappingException
1573
     */
1574 343
    protected function _validateAndCompleteOneToOneMapping(array $mapping)
1575
    {
1576 343
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1577
1578 337
        if (isset($mapping['joinColumns']) && $mapping['joinColumns']) {
1579 238
            $mapping['isOwningSide'] = true;
1580
        }
1581
1582 337
        if ($mapping['isOwningSide']) {
1583 323
            if (empty($mapping['joinColumns'])) {
1584
                // Apply default join column
1585 107
                $mapping['joinColumns'] = [
1586
                    [
1587 107
                        'name' => $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name),
0 ignored issues
show
Unused Code introduced by
The call to Doctrine\ORM\Mapping\Nam...ategy::joinColumnName() has too many arguments starting with $this->name. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1587
                        'name' => $this->namingStrategy->/** @scrutinizer ignore-call */ joinColumnName($mapping['fieldName'], $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. Please note the @ignore annotation hint above.

Loading history...
1588 107
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName()
1589
                    ]
1590
                ];
1591
            }
1592
1593 323
            $uniqueConstraintColumns = [];
1594
1595 323
            foreach ($mapping['joinColumns'] as &$joinColumn) {
1596 323
                if ($mapping['type'] === self::ONE_TO_ONE && ! $this->isInheritanceTypeSingleTable()) {
1597 172
                    if (count($mapping['joinColumns']) === 1) {
1598 170
                        if (empty($mapping['id'])) {
1599 170
                            $joinColumn['unique'] = true;
1600
                        }
1601
                    } else {
1602 2
                        $uniqueConstraintColumns[] = $joinColumn['name'];
1603
                    }
1604
                }
1605
1606 323
                if (empty($joinColumn['name'])) {
1607 37
                    $joinColumn['name'] = $this->namingStrategy->joinColumnName($mapping['fieldName'], $this->name);
1608
                }
1609
1610 323
                if (empty($joinColumn['referencedColumnName'])) {
1611 6
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1612
                }
1613
1614 323
                if ($joinColumn['name'][0] === '`') {
1615 8
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1616 8
                    $joinColumn['quoted'] = true;
1617
                }
1618
1619 323
                if ($joinColumn['referencedColumnName'][0] === '`') {
1620 5
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1621 5
                    $joinColumn['quoted']               = true;
1622
                }
1623
1624 323
                $mapping['sourceToTargetKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1625 323
                $mapping['joinColumnFieldNames'][$joinColumn['name']] = $joinColumn['fieldName'] ?? $joinColumn['name'];
1626
            }
1627
1628 323
            if ($uniqueConstraintColumns) {
1629 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...
1630
                    throw new RuntimeException("ClassMetadataInfo::setTable() has to be called before defining a one to one relationship.");
1631
                }
1632
1633 2
                $this->table['uniqueConstraints'][$mapping['fieldName'] . "_uniq"] = [
1634 2
                    'columns' => $uniqueConstraintColumns
1635
                ];
1636
            }
1637
1638 323
            $mapping['targetToSourceKeyColumns'] = array_flip($mapping['sourceToTargetKeyColumns']);
1639
        }
1640
1641 337
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1642 337
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1643
1644 337
        if ($mapping['orphanRemoval']) {
1645 22
            unset($mapping['unique']);
1646
        }
1647
1648 337
        if (isset($mapping['id']) && $mapping['id'] === true && !$mapping['isOwningSide']) {
1649 2
            throw MappingException::illegalInverseIdentifierAssociation($this->name, $mapping['fieldName']);
1650
        }
1651
1652 335
        return $mapping;
1653
    }
1654
1655
    /**
1656
     * Validates & completes a one-to-many association mapping.
1657
     *
1658
     * @param array $mapping The mapping to validate and complete.
1659
     *
1660
     * @return array The validated and completed mapping.
1661
     *
1662
     * @throws MappingException
1663
     * @throws InvalidArgumentException
1664
     */
1665 153
    protected function _validateAndCompleteOneToManyMapping(array $mapping)
1666
    {
1667 153
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1668
1669
        // OneToMany-side MUST be inverse (must have mappedBy)
1670 152
        if ( ! isset($mapping['mappedBy'])) {
1671
            throw MappingException::oneToManyRequiresMappedBy($mapping['fieldName']);
1672
        }
1673
1674 152
        $mapping['orphanRemoval']   = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1675 152
        $mapping['isCascadeRemove'] = $mapping['orphanRemoval'] || $mapping['isCascadeRemove'];
1676
1677 152
        $this->assertMappingOrderBy($mapping);
1678
1679 152
        return $mapping;
1680
    }
1681
1682
    /**
1683
     * Validates & completes a many-to-many association mapping.
1684
     *
1685
     * @param array $mapping The mapping to validate & complete.
1686
     *
1687
     * @return array The validated & completed mapping.
1688
     *
1689
     * @throws \InvalidArgumentException
1690
     */
1691 176
    protected function _validateAndCompleteManyToManyMapping(array $mapping)
1692
    {
1693 176
        $mapping = $this->_validateAndCompleteAssociationMapping($mapping);
1694
1695 174
        if ($mapping['isOwningSide']) {
1696
            // owning side MUST have a join table
1697 156
            if ( ! isset($mapping['joinTable']['name'])) {
1698 30
                $mapping['joinTable']['name'] = $this->namingStrategy->joinTableName($mapping['sourceEntity'], $mapping['targetEntity'], $mapping['fieldName']);
1699
            }
1700
1701 156
            $selfReferencingEntityWithoutJoinColumns = $mapping['sourceEntity'] == $mapping['targetEntity']
1702 156
                && (! (isset($mapping['joinTable']['joinColumns']) || isset($mapping['joinTable']['inverseJoinColumns'])));
1703
1704 156
            if ( ! isset($mapping['joinTable']['joinColumns'])) {
1705 30
                $mapping['joinTable']['joinColumns'] = [
1706
                    [
1707 30
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $selfReferencingEntityWithoutJoinColumns ? 'source' : null),
1708 30
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1709 30
                        'onDelete' => 'CASCADE'
1710
                    ]
1711
                ];
1712
            }
1713
1714 156
            if ( ! isset($mapping['joinTable']['inverseJoinColumns'])) {
1715 30
                $mapping['joinTable']['inverseJoinColumns'] = [
1716
                    [
1717 30
                        'name' => $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $selfReferencingEntityWithoutJoinColumns ? 'target' : null),
1718 30
                        'referencedColumnName' => $this->namingStrategy->referenceColumnName(),
1719 30
                        'onDelete' => 'CASCADE'
1720
                    ]
1721
                ];
1722
            }
1723
1724 156
            $mapping['joinTableColumns'] = [];
1725
1726 156
            foreach ($mapping['joinTable']['joinColumns'] as &$joinColumn) {
1727 156
                if (empty($joinColumn['name'])) {
1728 2
                    $joinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['sourceEntity'], $joinColumn['referencedColumnName']);
1729
                }
1730
1731 156
                if (empty($joinColumn['referencedColumnName'])) {
1732 6
                    $joinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1733
                }
1734
1735 156
                if ($joinColumn['name'][0] === '`') {
1736 3
                    $joinColumn['name']   = trim($joinColumn['name'], '`');
1737 3
                    $joinColumn['quoted'] = true;
1738
                }
1739
1740 156
                if ($joinColumn['referencedColumnName'][0] === '`') {
1741 3
                    $joinColumn['referencedColumnName'] = trim($joinColumn['referencedColumnName'], '`');
1742 3
                    $joinColumn['quoted']               = true;
1743
                }
1744
1745 156
                if (isset($joinColumn['onDelete']) && strtolower($joinColumn['onDelete']) == 'cascade') {
1746 41
                    $mapping['isOnDeleteCascade'] = true;
1747
                }
1748
1749 156
                $mapping['relationToSourceKeyColumns'][$joinColumn['name']] = $joinColumn['referencedColumnName'];
1750 156
                $mapping['joinTableColumns'][] = $joinColumn['name'];
1751
            }
1752
1753 156
            foreach ($mapping['joinTable']['inverseJoinColumns'] as &$inverseJoinColumn) {
1754 156
                if (empty($inverseJoinColumn['name'])) {
1755 2
                    $inverseJoinColumn['name'] = $this->namingStrategy->joinKeyColumnName($mapping['targetEntity'], $inverseJoinColumn['referencedColumnName']);
1756
                }
1757
1758 156
                if (empty($inverseJoinColumn['referencedColumnName'])) {
1759 6
                    $inverseJoinColumn['referencedColumnName'] = $this->namingStrategy->referenceColumnName();
1760
                }
1761
1762 156
                if ($inverseJoinColumn['name'][0] === '`') {
1763 3
                    $inverseJoinColumn['name']   = trim($inverseJoinColumn['name'], '`');
1764 3
                    $inverseJoinColumn['quoted'] = true;
1765
                }
1766
1767 156
                if ($inverseJoinColumn['referencedColumnName'][0] === '`') {
1768 3
                    $inverseJoinColumn['referencedColumnName']  = trim($inverseJoinColumn['referencedColumnName'], '`');
1769 3
                    $inverseJoinColumn['quoted']                = true;
1770
                }
1771
1772 156
                if (isset($inverseJoinColumn['onDelete']) && strtolower($inverseJoinColumn['onDelete']) == 'cascade') {
1773 36
                    $mapping['isOnDeleteCascade'] = true;
1774
                }
1775
1776 156
                $mapping['relationToTargetKeyColumns'][$inverseJoinColumn['name']] = $inverseJoinColumn['referencedColumnName'];
1777 156
                $mapping['joinTableColumns'][] = $inverseJoinColumn['name'];
1778
            }
1779
        }
1780
1781 174
        $mapping['orphanRemoval'] = isset($mapping['orphanRemoval']) && $mapping['orphanRemoval'];
1782
1783 174
        $this->assertMappingOrderBy($mapping);
1784
1785 174
        return $mapping;
1786
    }
1787
1788
    /**
1789
     * {@inheritDoc}
1790
     */
1791 659
    public function getIdentifierFieldNames()
1792
    {
1793 659
        return $this->identifier;
1794
    }
1795
1796
    /**
1797
     * Gets the name of the single id field. Note that this only works on
1798
     * entity classes that have a single-field pk.
1799
     *
1800
     * @return string
1801
     *
1802
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1803
     */
1804 1221
    public function getSingleIdentifierFieldName()
1805
    {
1806 1221
        if ($this->isIdentifierComposite) {
1807 1
            throw MappingException::singleIdNotAllowedOnCompositePrimaryKey($this->name);
1808
        }
1809
1810 1220
        if ( ! isset($this->identifier[0])) {
1811 1
            throw MappingException::noIdDefined($this->name);
1812
        }
1813
1814 1219
        return $this->identifier[0];
1815
    }
1816
1817
    /**
1818
     * Gets the column name of the single id column. Note that this only works on
1819
     * entity classes that have a single-field pk.
1820
     *
1821
     * @return string
1822
     *
1823
     * @throws MappingException If the class doesn't have an identifier or it has a composite primary key.
1824
     */
1825 3
    public function getSingleIdentifierColumnName()
1826
    {
1827 3
        return $this->getColumnName($this->getSingleIdentifierFieldName());
1828
    }
1829
1830
    /**
1831
     * INTERNAL:
1832
     * Sets the mapped identifier/primary key fields of this class.
1833
     * Mainly used by the ClassMetadataFactory to assign inherited identifiers.
1834
     *
1835
     * @param array $identifier
1836
     *
1837
     * @return void
1838
     */
1839 148
    public function setIdentifier(array $identifier)
1840
    {
1841 148
        $this->identifier = $identifier;
1842 148
        $this->isIdentifierComposite = (count($this->identifier) > 1);
1843 148
    }
1844
1845
    /**
1846
     * {@inheritDoc}
1847
     */
1848 66
    public function getIdentifier()
1849
    {
1850 66
        return $this->identifier;
1851
    }
1852
1853
    /**
1854
     * {@inheritDoc}
1855
     */
1856 317
    public function hasField($fieldName)
1857
    {
1858 317
        return isset($this->fieldMappings[$fieldName]) || isset($this->embeddedClasses[$fieldName]);
1859
    }
1860
1861
    /**
1862
     * Gets an array containing all the column names.
1863
     *
1864
     * @param array|null $fieldNames
1865
     *
1866
     * @return array
1867
     */
1868 50
    public function getColumnNames(array $fieldNames = null)
1869
    {
1870 50
        if (null === $fieldNames) {
1871 49
            return array_keys($this->fieldNames);
1872
        }
1873
1874 1
        return array_values(array_map([$this, 'getColumnName'], $fieldNames));
1875
    }
1876
1877
    /**
1878
     * Returns an array with all the identifier column names.
1879
     *
1880
     * @return array
1881
     */
1882 337
    public function getIdentifierColumnNames()
1883
    {
1884 337
        $columnNames = [];
1885
1886 337
        foreach ($this->identifier as $idProperty) {
1887 337
            if (isset($this->fieldMappings[$idProperty])) {
1888 332
                $columnNames[] = $this->fieldMappings[$idProperty]['columnName'];
1889
1890 332
                continue;
1891
            }
1892
1893
            // Association defined as Id field
1894 23
            $joinColumns      = $this->associationMappings[$idProperty]['joinColumns'];
1895
            $assocColumnNames = array_map(function ($joinColumn) { return $joinColumn['name']; }, $joinColumns);
1896
1897 23
            $columnNames = array_merge($columnNames, $assocColumnNames);
1898
        }
1899
1900 337
        return $columnNames;
1901
    }
1902
1903
    /**
1904
     * Sets the type of Id generator to use for the mapped class.
1905
     *
1906
     * @param int $generatorType
1907
     *
1908
     * @return void
1909
     */
1910 541
    public function setIdGeneratorType($generatorType)
1911
    {
1912 541
        $this->generatorType = $generatorType;
1913 541
    }
1914
1915
    /**
1916
     * Checks whether the mapped class uses an Id generator.
1917
     *
1918
     * @return boolean TRUE if the mapped class uses an Id generator, FALSE otherwise.
1919
     */
1920 478
    public function usesIdGenerator()
1921
    {
1922 478
        return $this->generatorType != self::GENERATOR_TYPE_NONE;
1923
    }
1924
1925
    /**
1926
     * @return boolean
1927
     */
1928 1451
    public function isInheritanceTypeNone()
1929
    {
1930 1451
        return $this->inheritanceType == self::INHERITANCE_TYPE_NONE;
1931
    }
1932
1933
    /**
1934
     * Checks whether the mapped class uses the JOINED inheritance mapping strategy.
1935
     *
1936
     * @return boolean TRUE if the class participates in a JOINED inheritance mapping,
1937
     *                 FALSE otherwise.
1938
     */
1939 1131
    public function isInheritanceTypeJoined()
1940
    {
1941 1131
        return $this->inheritanceType == self::INHERITANCE_TYPE_JOINED;
1942
    }
1943
1944
    /**
1945
     * Checks whether the mapped class uses the SINGLE_TABLE inheritance mapping strategy.
1946
     *
1947
     * @return boolean TRUE if the class participates in a SINGLE_TABLE inheritance mapping,
1948
     *                 FALSE otherwise.
1949
     */
1950 1335
    public function isInheritanceTypeSingleTable()
1951
    {
1952 1335
        return $this->inheritanceType == self::INHERITANCE_TYPE_SINGLE_TABLE;
1953
    }
1954
1955
    /**
1956
     * Checks whether the mapped class uses the TABLE_PER_CLASS inheritance mapping strategy.
1957
     *
1958
     * @return boolean TRUE if the class participates in a TABLE_PER_CLASS inheritance mapping,
1959
     *                 FALSE otherwise.
1960
     */
1961 298
    public function isInheritanceTypeTablePerClass()
1962
    {
1963 298
        return $this->inheritanceType == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
1964
    }
1965
1966
    /**
1967
     * Checks whether the class uses an identity column for the Id generation.
1968
     *
1969
     * @return boolean TRUE if the class uses the IDENTITY generator, FALSE otherwise.
1970
     */
1971 1158
    public function isIdGeneratorIdentity()
1972
    {
1973 1158
        return $this->generatorType == self::GENERATOR_TYPE_IDENTITY;
1974
    }
1975
1976
    /**
1977
     * Checks whether the class uses a sequence for id generation.
1978
     *
1979
     * @return boolean TRUE if the class uses the SEQUENCE generator, FALSE otherwise.
1980
     */
1981 371
    public function isIdGeneratorSequence()
1982
    {
1983 371
        return $this->generatorType == self::GENERATOR_TYPE_SEQUENCE;
1984
    }
1985
1986
    /**
1987
     * Checks whether the class uses a table for id generation.
1988
     *
1989
     * @return boolean TRUE if the class uses the TABLE generator, FALSE otherwise.
1990
     */
1991 102
    public function isIdGeneratorTable()
1992
    {
1993 102
        return $this->generatorType == self::GENERATOR_TYPE_TABLE;
1994
    }
1995
1996
    /**
1997
     * Checks whether the class has a natural identifier/pk (which means it does
1998
     * not use any Id generator.
1999
     *
2000
     * @return boolean
2001
     */
2002 77
    public function isIdentifierNatural()
2003
    {
2004 77
        return $this->generatorType == self::GENERATOR_TYPE_NONE;
2005
    }
2006
2007
    /**
2008
     * Checks whether the class use a UUID for id generation.
2009
     *
2010
     * @return boolean
2011
     */
2012
    public function isIdentifierUuid()
2013
    {
2014
        return $this->generatorType == self::GENERATOR_TYPE_UUID;
2015
    }
2016
2017
    /**
2018
     * Gets the type of a field.
2019
     *
2020
     * @param string $fieldName
2021
     *
2022
     * @return string|null
2023
     *
2024
     * @todo 3.0 Remove this. PersisterHelper should fix it somehow
2025
     */
2026 982
    public function getTypeOfField($fieldName)
2027
    {
2028 982
        return isset($this->fieldMappings[$fieldName])
2029 982
            ? $this->fieldMappings[$fieldName]['type']
2030 982
            : null;
2031
    }
2032
2033
    /**
2034
     * Gets the type of a column.
2035
     *
2036
     * @param string $columnName
2037
     *
2038
     * @return string|null
2039
     *
2040
     * @deprecated 3.0 remove this. this method is bogus and unreliable, since it cannot resolve the type of a column
2041
     *             that is derived by a referenced field on a different entity.
2042
     */
2043
    public function getTypeOfColumn($columnName)
2044
    {
2045
        return $this->getTypeOfField($this->getFieldName($columnName));
2046
    }
2047
2048
    /**
2049
     * Gets the name of the primary table.
2050
     *
2051
     * @return string
2052
     */
2053 1441
    public function getTableName()
2054
    {
2055 1441
        return $this->table['name'];
2056
    }
2057
2058
    /**
2059
     * Gets primary table's schema name.
2060
     *
2061
     * @return string|null
2062
     */
2063 13
    public function getSchemaName()
2064
    {
2065 13
        return isset($this->table['schema']) ? $this->table['schema'] : null;
2066
    }
2067
2068
    /**
2069
     * Gets the table name to use for temporary identifier tables of this class.
2070
     *
2071
     * @return string
2072
     */
2073 7
    public function getTemporaryIdTableName()
2074
    {
2075
        // replace dots with underscores because PostgreSQL creates temporary tables in a special schema
2076 7
        return str_replace('.', '_', $this->getTableName() . '_id_tmp');
2077
    }
2078
2079
    /**
2080
     * Sets the mapped subclasses of this class.
2081
     *
2082
     * @param array $subclasses The names of all mapped subclasses.
2083
     *
2084
     * @return void
2085
     */
2086 2
    public function setSubclasses(array $subclasses)
2087
    {
2088 2
        foreach ($subclasses as $subclass) {
2089 2
            $this->subClasses[] = $this->fullyQualifiedClassName($subclass);
2090
        }
2091 2
    }
2092
2093
    /**
2094
     * Sets the parent class names.
2095
     * Assumes that the class names in the passed array are in the order:
2096
     * directParent -> directParentParent -> directParentParentParent ... -> root.
2097
     *
2098
     * @param array $classNames
2099
     *
2100
     * @return void
2101
     */
2102 495
    public function setParentClasses(array $classNames)
2103
    {
2104 495
        $this->parentClasses = $classNames;
2105
2106 495
        if (count($classNames) > 0) {
2107 101
            $this->rootEntityName = array_pop($classNames);
2108
        }
2109 495
    }
2110
2111
    /**
2112
     * Sets the inheritance type used by the class and its subclasses.
2113
     *
2114
     * @param integer $type
2115
     *
2116
     * @return void
2117
     *
2118
     * @throws MappingException
2119
     */
2120 198
    public function setInheritanceType($type)
2121
    {
2122 198
        if ( ! $this->_isInheritanceType($type)) {
2123
            throw MappingException::invalidInheritanceType($this->name, $type);
2124
        }
2125
2126 198
        $this->inheritanceType = $type;
2127 198
    }
2128
2129
    /**
2130
     * Sets the association to override association mapping of property for an entity relationship.
2131
     *
2132
     * @param string $fieldName
2133
     * @param array  $overrideMapping
2134
     *
2135
     * @return void
2136
     *
2137
     * @throws MappingException
2138
     */
2139 26
    public function setAssociationOverride($fieldName, array $overrideMapping)
2140
    {
2141 26
        if ( ! isset($this->associationMappings[$fieldName])) {
2142 1
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2143
        }
2144
2145 25
        $mapping = $this->associationMappings[$fieldName];
2146
2147 25
        if (isset($overrideMapping['joinColumns'])) {
2148 13
            $mapping['joinColumns'] = $overrideMapping['joinColumns'];
2149
        }
2150
2151 25
        if (isset($overrideMapping['inversedBy'])) {
2152 6
            $mapping['inversedBy'] = $overrideMapping['inversedBy'];
2153
        }
2154
2155 25
        if (isset($overrideMapping['joinTable'])) {
2156 12
            $mapping['joinTable'] = $overrideMapping['joinTable'];
2157
        }
2158
2159 25
        if (isset($overrideMapping['fetch'])) {
2160 6
            $mapping['fetch'] = $overrideMapping['fetch'];
2161
        }
2162
2163 25
        $mapping['joinColumnFieldNames']        = null;
2164 25
        $mapping['joinTableColumns']            = null;
2165 25
        $mapping['sourceToTargetKeyColumns']    = null;
2166 25
        $mapping['relationToSourceKeyColumns']  = null;
2167 25
        $mapping['relationToTargetKeyColumns']  = null;
2168
2169 25
        switch ($mapping['type']) {
2170 25
            case self::ONE_TO_ONE:
2171 1
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2172 1
                break;
2173 24
            case self::ONE_TO_MANY:
2174
                $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2175
                break;
2176 24
            case self::MANY_TO_ONE:
2177 12
                $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2178 12
                break;
2179 24
            case self::MANY_TO_MANY:
2180 24
                $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2181 24
                break;
2182
        }
2183
2184 25
        $this->associationMappings[$fieldName] = $mapping;
2185 25
    }
2186
2187
    /**
2188
     * Sets the override for a mapped field.
2189
     *
2190
     * @param string $fieldName
2191
     * @param array  $overrideMapping
2192
     *
2193
     * @return void
2194
     *
2195
     * @throws MappingException
2196
     */
2197 15
    public function setAttributeOverride($fieldName, array $overrideMapping)
2198
    {
2199 15
        if ( ! isset($this->fieldMappings[$fieldName])) {
2200 1
            throw MappingException::invalidOverrideFieldName($this->name, $fieldName);
2201
        }
2202
2203 14
        $mapping = $this->fieldMappings[$fieldName];
2204
2205 14
        if (isset($mapping['id'])) {
2206 12
            $overrideMapping['id'] = $mapping['id'];
2207
        }
2208
2209 14
        if ( ! isset($overrideMapping['type'])) {
2210 6
            $overrideMapping['type'] = $mapping['type'];
2211
        }
2212
2213 14
        if ( ! isset($overrideMapping['fieldName'])) {
2214 5
            $overrideMapping['fieldName'] = $mapping['fieldName'];
2215
        }
2216
2217 14
        if ($overrideMapping['type'] !== $mapping['type']) {
2218 1
            throw MappingException::invalidOverrideFieldType($this->name, $fieldName);
2219
        }
2220
2221 13
        unset($this->fieldMappings[$fieldName]);
2222 13
        unset($this->fieldNames[$mapping['columnName']]);
2223 13
        unset($this->columnNames[$mapping['fieldName']]);
2224
2225 13
        $this->_validateAndCompleteFieldMapping($overrideMapping);
2226
2227 13
        $this->fieldMappings[$fieldName] = $overrideMapping;
2228 13
    }
2229
2230
    /**
2231
     * Checks whether a mapped field is inherited from an entity superclass.
2232
     *
2233
     * @param string $fieldName
2234
     *
2235
     * @return bool TRUE if the field is inherited, FALSE otherwise.
2236
     */
2237 466
    public function isInheritedField($fieldName)
2238
    {
2239 466
        return isset($this->fieldMappings[$fieldName]['inherited']);
2240
    }
2241
2242
    /**
2243
     * Checks if this entity is the root in any entity-inheritance-hierarchy.
2244
     *
2245
     * @return bool
2246
     */
2247 494
    public function isRootEntity()
2248
    {
2249 494
        return $this->name == $this->rootEntityName;
2250
    }
2251
2252
    /**
2253
     * Checks whether a mapped association field is inherited from a superclass.
2254
     *
2255
     * @param string $fieldName
2256
     *
2257
     * @return boolean TRUE if the field is inherited, FALSE otherwise.
2258
     */
2259 429
    public function isInheritedAssociation($fieldName)
2260
    {
2261 429
        return isset($this->associationMappings[$fieldName]['inherited']);
2262
    }
2263
2264 429
    public function isInheritedEmbeddedClass($fieldName)
2265
    {
2266 429
        return isset($this->embeddedClasses[$fieldName]['inherited']);
2267
    }
2268
2269
    /**
2270
     * Sets the name of the primary table the class is mapped to.
2271
     *
2272
     * @param string $tableName The table name.
2273
     *
2274
     * @return void
2275
     *
2276
     * @deprecated Use {@link setPrimaryTable}.
2277
     */
2278 5
    public function setTableName($tableName)
2279
    {
2280 5
        $this->table['name'] = $tableName;
2281 5
    }
2282
2283
    /**
2284
     * Sets the primary table definition. The provided array supports the
2285
     * following structure:
2286
     *
2287
     * name => <tableName> (optional, defaults to class name)
2288
     * indexes => array of indexes (optional)
2289
     * uniqueConstraints => array of constraints (optional)
2290
     *
2291
     * If a key is omitted, the current value is kept.
2292
     *
2293
     * @param array $table The table description.
2294
     *
2295
     * @return void
2296
     */
2297 372
    public function setPrimaryTable(array $table)
2298
    {
2299 372
        if (isset($table['name'])) {
2300
            // Split schema and table name from a table name like "myschema.mytable"
2301 299
            if (strpos($table['name'], '.') !== false) {
2302 9
                list($this->table['schema'], $table['name']) = explode('.', $table['name'], 2);
2303
            }
2304
2305 299
            if ($table['name'][0] === '`') {
2306 18
                $table['name']          = trim($table['name'], '`');
2307 18
                $this->table['quoted']  = true;
2308
            }
2309
2310 299
            $this->table['name'] = $table['name'];
2311
        }
2312
2313 372
        if (isset($table['quoted'])) {
2314 2
            $this->table['quoted'] = $table['quoted'];
2315
        }
2316
2317 372
        if (isset($table['schema'])) {
2318 7
            $this->table['schema'] = $table['schema'];
2319
        }
2320
2321 372
        if (isset($table['indexes'])) {
2322 18
            $this->table['indexes'] = $table['indexes'];
2323
        }
2324
2325 372
        if (isset($table['uniqueConstraints'])) {
2326 9
            $this->table['uniqueConstraints'] = $table['uniqueConstraints'];
2327
        }
2328
2329 372
        if (isset($table['options'])) {
2330 11
            $this->table['options'] = $table['options'];
2331
        }
2332 372
    }
2333
2334
    /**
2335
     * Checks whether the given type identifies an inheritance type.
2336
     *
2337
     * @param integer $type
2338
     *
2339
     * @return boolean TRUE if the given type identifies an inheritance type, FALSe otherwise.
2340
     */
2341 198
    private function _isInheritanceType($type)
2342
    {
2343 198
        return $type == self::INHERITANCE_TYPE_NONE ||
2344 125
                $type == self::INHERITANCE_TYPE_SINGLE_TABLE ||
2345 64
                $type == self::INHERITANCE_TYPE_JOINED ||
2346 198
                $type == self::INHERITANCE_TYPE_TABLE_PER_CLASS;
2347
    }
2348
2349
    /**
2350
     * Adds a mapped field to the class.
2351
     *
2352
     * @param array $mapping The field mapping.
2353
     *
2354
     * @return void
2355
     *
2356
     * @throws MappingException
2357
     */
2358 637
    public function mapField(array $mapping)
2359
    {
2360 637
        $this->_validateAndCompleteFieldMapping($mapping);
2361 635
        $this->assertFieldNotMapped($mapping['fieldName']);
2362
2363 634
        $this->fieldMappings[$mapping['fieldName']] = $mapping;
2364 634
    }
2365
2366
    /**
2367
     * INTERNAL:
2368
     * Adds an association mapping without completing/validating it.
2369
     * This is mainly used to add inherited association mappings to derived classes.
2370
     *
2371
     * @param array $mapping
2372
     *
2373
     * @return void
2374
     *
2375
     * @throws MappingException
2376
     */
2377 59
    public function addInheritedAssociationMapping(array $mapping/*, $owningClassName = null*/)
2378
    {
2379 59
        if (isset($this->associationMappings[$mapping['fieldName']])) {
2380 1
            throw MappingException::duplicateAssociationMapping($this->name, $mapping['fieldName']);
2381
        }
2382 59
        $this->associationMappings[$mapping['fieldName']] = $mapping;
2383 59
    }
2384
2385
    /**
2386
     * INTERNAL:
2387
     * Adds a field mapping without completing/validating it.
2388
     * This is mainly used to add inherited field mappings to derived classes.
2389
     *
2390
     * @param array $fieldMapping
2391
     *
2392
     * @return void
2393
     */
2394 132
    public function addInheritedFieldMapping(array $fieldMapping)
2395
    {
2396 132
        $this->fieldMappings[$fieldMapping['fieldName']] = $fieldMapping;
2397 132
        $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: 3.0 Remove this. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2397
        /** @scrutinizer ignore-deprecated */ $this->columnNames[$fieldMapping['fieldName']] = $fieldMapping['columnName'];

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...
2398 132
        $this->fieldNames[$fieldMapping['columnName']] = $fieldMapping['fieldName'];
2399 132
    }
2400
2401
    /**
2402
     * INTERNAL:
2403
     * Adds a named query to this class.
2404
     *
2405
     * @param array $queryMapping
2406
     *
2407
     * @return void
2408
     *
2409
     * @throws MappingException
2410
     */
2411 36
    public function addNamedQuery(array $queryMapping)
2412
    {
2413 36
        if (!isset($queryMapping['name'])) {
2414 2
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2415
        }
2416
2417 34
        if (isset($this->namedQueries[$queryMapping['name']])) {
2418 1
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2419
        }
2420
2421 34
        if (!isset($queryMapping['query'])) {
2422
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2423
        }
2424
2425 34
        $name   = $queryMapping['name'];
2426 34
        $query  = $queryMapping['query'];
2427 34
        $dql    = str_replace('__CLASS__', $this->name, $query);
2428
2429 34
        $this->namedQueries[$name] = [
2430 34
            'name'  => $name,
2431 34
            'query' => $query,
2432 34
            'dql'   => $dql,
2433
        ];
2434 34
    }
2435
2436
    /**
2437
     * INTERNAL:
2438
     * Adds a named native query to this class.
2439
     *
2440
     * @param array $queryMapping
2441
     *
2442
     * @return void
2443
     *
2444
     * @throws MappingException
2445
     */
2446 41
    public function addNamedNativeQuery(array $queryMapping)
2447
    {
2448 41
        if (!isset($queryMapping['name'])) {
2449
            throw MappingException::nameIsMandatoryForQueryMapping($this->name);
2450
        }
2451
2452 41
        if (isset($this->namedNativeQueries[$queryMapping['name']])) {
2453 1
            throw MappingException::duplicateQueryMapping($this->name, $queryMapping['name']);
2454
        }
2455
2456 41
        if (!isset($queryMapping['query'])) {
2457
            throw MappingException::emptyQueryMapping($this->name, $queryMapping['name']);
2458
        }
2459
2460 41
        if (!isset($queryMapping['resultClass']) && !isset($queryMapping['resultSetMapping'])) {
2461
            throw MappingException::missingQueryMapping($this->name, $queryMapping['name']);
2462
        }
2463
2464 41
        $queryMapping['isSelfClass'] = false;
2465
2466 41
        if (isset($queryMapping['resultClass'])) {
2467 39
            if ($queryMapping['resultClass'] === '__CLASS__') {
2468
2469 13
                $queryMapping['isSelfClass'] = true;
2470 13
                $queryMapping['resultClass'] = $this->name;
2471
            }
2472
2473 39
            $queryMapping['resultClass'] = $this->fullyQualifiedClassName($queryMapping['resultClass']);
2474 39
            $queryMapping['resultClass'] = ltrim($queryMapping['resultClass'], '\\');
2475
        }
2476
2477 41
        $this->namedNativeQueries[$queryMapping['name']] = $queryMapping;
2478 41
    }
2479
2480
    /**
2481
     * INTERNAL:
2482
     * Adds a sql result set mapping to this class.
2483
     *
2484
     * @param array $resultMapping
2485
     *
2486
     * @return void
2487
     *
2488
     * @throws MappingException
2489
     */
2490 41
    public function addSqlResultSetMapping(array $resultMapping)
2491
    {
2492 41
        if (!isset($resultMapping['name'])) {
2493
            throw MappingException::nameIsMandatoryForSqlResultSetMapping($this->name);
2494
        }
2495
2496 41
        if (isset($this->sqlResultSetMappings[$resultMapping['name']])) {
2497 1
            throw MappingException::duplicateResultSetMapping($this->name, $resultMapping['name']);
2498
        }
2499
2500 41
        if (isset($resultMapping['entities'])) {
2501 41
            foreach ($resultMapping['entities'] as $key => $entityResult) {
2502 41
                if (!isset($entityResult['entityClass'])) {
2503 1
                    throw MappingException::missingResultSetMappingEntity($this->name, $resultMapping['name']);
2504
                }
2505
2506 40
                $entityResult['isSelfClass'] = false;
2507 40
                if ($entityResult['entityClass'] === '__CLASS__') {
2508
2509 23
                    $entityResult['isSelfClass'] = true;
2510 23
                    $entityResult['entityClass'] = $this->name;
2511
2512
                }
2513
2514 40
                $entityResult['entityClass'] = $this->fullyQualifiedClassName($entityResult['entityClass']);
2515
2516 40
                $resultMapping['entities'][$key]['entityClass'] = ltrim($entityResult['entityClass'], '\\');
2517 40
                $resultMapping['entities'][$key]['isSelfClass'] = $entityResult['isSelfClass'];
2518
2519 40
                if (isset($entityResult['fields'])) {
2520 36
                    foreach ($entityResult['fields'] as $k => $field) {
2521 36
                        if (!isset($field['name'])) {
2522
                            throw MappingException::missingResultSetMappingFieldName($this->name, $resultMapping['name']);
2523
                        }
2524
2525 36
                        if (!isset($field['column'])) {
2526 17
                            $fieldName = $field['name'];
2527 17
                            if (strpos($fieldName, '.')) {
2528 10
                                list(, $fieldName) = explode('.', $fieldName);
2529
                            }
2530
2531 40
                            $resultMapping['entities'][$key]['fields'][$k]['column'] = $fieldName;
2532
                        }
2533
                    }
2534
                }
2535
            }
2536
        }
2537
2538 40
        $this->sqlResultSetMappings[$resultMapping['name']] = $resultMapping;
2539 40
    }
2540
2541
    /**
2542
     * Adds a one-to-one mapping.
2543
     *
2544
     * @param array $mapping The mapping.
2545
     *
2546
     * @return void
2547
     */
2548 192
    public function mapOneToOne(array $mapping)
2549
    {
2550 192
        $mapping['type'] = self::ONE_TO_ONE;
2551
2552 192
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2553
2554 189
        $this->_storeAssociationMapping($mapping);
2555 188
    }
2556
2557
    /**
2558
     * Adds a one-to-many mapping.
2559
     *
2560
     * @param array $mapping The mapping.
2561
     *
2562
     * @return void
2563
     */
2564 153
    public function mapOneToMany(array $mapping)
2565
    {
2566 153
        $mapping['type'] = self::ONE_TO_MANY;
2567
2568 153
        $mapping = $this->_validateAndCompleteOneToManyMapping($mapping);
2569
2570 152
        $this->_storeAssociationMapping($mapping);
2571 152
    }
2572
2573
    /**
2574
     * Adds a many-to-one mapping.
2575
     *
2576
     * @param array $mapping The mapping.
2577
     *
2578
     * @return void
2579
     */
2580 187
    public function mapManyToOne(array $mapping)
2581
    {
2582 187
        $mapping['type'] = self::MANY_TO_ONE;
2583
2584
        // A many-to-one mapping is essentially a one-one backreference
2585 187
        $mapping = $this->_validateAndCompleteOneToOneMapping($mapping);
2586
2587 182
        $this->_storeAssociationMapping($mapping);
2588 182
    }
2589
2590
    /**
2591
     * Adds a many-to-many mapping.
2592
     *
2593
     * @param array $mapping The mapping.
2594
     *
2595
     * @return void
2596
     */
2597 176
    public function mapManyToMany(array $mapping)
2598
    {
2599 176
        $mapping['type'] = self::MANY_TO_MANY;
2600
2601 176
        $mapping = $this->_validateAndCompleteManyToManyMapping($mapping);
2602
2603 174
        $this->_storeAssociationMapping($mapping);
2604 174
    }
2605
2606
    /**
2607
     * Stores the association mapping.
2608
     *
2609
     * @param array $assocMapping
2610
     *
2611
     * @return void
2612
     *
2613
     * @throws MappingException
2614
     */
2615 395
    protected function _storeAssociationMapping(array $assocMapping)
2616
    {
2617 395
        $sourceFieldName = $assocMapping['fieldName'];
2618
2619 395
        $this->assertFieldNotMapped($sourceFieldName);
2620
2621 394
        $this->associationMappings[$sourceFieldName] = $assocMapping;
2622 394
    }
2623
2624
    /**
2625
     * Registers a custom repository class for the entity class.
2626
     *
2627
     * @param string $repositoryClassName The class name of the custom mapper.
2628
     *
2629
     * @return void
2630
     */
2631 64
    public function setCustomRepositoryClass($repositoryClassName)
2632
    {
2633 64
        $this->customRepositoryClassName = $this->fullyQualifiedClassName($repositoryClassName);
2634 64
    }
2635
2636
    /**
2637
     * Dispatches the lifecycle event of the given entity to the registered
2638
     * lifecycle callbacks and lifecycle listeners.
2639
     *
2640
     * @deprecated Deprecated since version 2.4 in favor of \Doctrine\ORM\Event\ListenersInvoker
2641
     *
2642
     * @param string $lifecycleEvent The lifecycle event.
2643
     * @param object $entity         The Entity on which the event occurred.
2644
     *
2645
     * @return void
2646
     */
2647 1
    public function invokeLifecycleCallbacks($lifecycleEvent, $entity)
2648
    {
2649 1
        foreach ($this->lifecycleCallbacks[$lifecycleEvent] as $callback) {
2650 1
            $entity->$callback();
2651
        }
2652 1
    }
2653
2654
    /**
2655
     * Whether the class has any attached lifecycle listeners or callbacks for a lifecycle event.
2656
     *
2657
     * @param string $lifecycleEvent
2658
     *
2659
     * @return boolean
2660
     */
2661
    public function hasLifecycleCallbacks($lifecycleEvent)
2662
    {
2663
        return isset($this->lifecycleCallbacks[$lifecycleEvent]);
2664
    }
2665
2666
    /**
2667
     * Gets the registered lifecycle callbacks for an event.
2668
     *
2669
     * @param string $event
2670
     *
2671
     * @return array
2672
     */
2673
    public function getLifecycleCallbacks($event)
2674
    {
2675
        return isset($this->lifecycleCallbacks[$event]) ? $this->lifecycleCallbacks[$event] : [];
2676
    }
2677
2678
    /**
2679
     * Adds a lifecycle callback for entities of this class.
2680
     *
2681
     * @param string $callback
2682
     * @param string $event
2683
     *
2684
     * @return void
2685
     */
2686 48
    public function addLifecycleCallback($callback, $event)
2687
    {
2688 48
        if (isset($this->lifecycleCallbacks[$event]) && in_array($callback, $this->lifecycleCallbacks[$event])) {
2689 3
            return;
2690
        }
2691
2692 48
        $this->lifecycleCallbacks[$event][] = $callback;
2693 48
    }
2694
2695
    /**
2696
     * Sets the lifecycle callbacks for entities of this class.
2697
     * Any previously registered callbacks are overwritten.
2698
     *
2699
     * @param array $callbacks
2700
     *
2701
     * @return void
2702
     */
2703 147
    public function setLifecycleCallbacks(array $callbacks)
2704
    {
2705 147
        $this->lifecycleCallbacks = $callbacks;
2706 147
    }
2707
2708
    /**
2709
     * Adds a entity listener for entities of this class.
2710
     *
2711
     * @param string $eventName The entity lifecycle event.
2712
     * @param string $class     The listener class.
2713
     * @param string $method    The listener callback method.
2714
     *
2715
     * @throws \Doctrine\ORM\Mapping\MappingException
2716
     */
2717 43
    public function addEntityListener($eventName, $class, $method)
2718
    {
2719 43
        $class    = $this->fullyQualifiedClassName($class);
2720
2721
        $listener = [
2722 43
            'class'  => $class,
2723 43
            'method' => $method,
2724
        ];
2725
2726 43
        if ( ! class_exists($class)) {
2727 1
            throw MappingException::entityListenerClassNotFound($class, $this->name);
2728
        }
2729
2730 42
        if ( ! method_exists($class, $method)) {
2731 1
            throw MappingException::entityListenerMethodNotFound($class, $method, $this->name);
2732
        }
2733
2734 41
        if (isset($this->entityListeners[$eventName]) && in_array($listener, $this->entityListeners[$eventName])) {
2735 1
            throw MappingException::duplicateEntityListener($class, $method, $this->name);
2736
        }
2737
2738 41
        $this->entityListeners[$eventName][] = $listener;
2739 41
    }
2740
2741
    /**
2742
     * Sets the discriminator column definition.
2743
     *
2744
     * @param array $columnDef
2745
     *
2746
     * @return void
2747
     *
2748
     * @throws MappingException
2749
     *
2750
     * @see getDiscriminatorColumn()
2751
     */
2752 190
    public function setDiscriminatorColumn($columnDef)
2753
    {
2754 190
        if ($columnDef !== null) {
0 ignored issues
show
introduced by
The condition $columnDef !== null is always true.
Loading history...
2755 129
            if ( ! isset($columnDef['name'])) {
2756 1
                throw MappingException::nameIsMandatoryForDiscriminatorColumns($this->name);
2757
            }
2758
2759 128
            if (isset($this->fieldNames[$columnDef['name']])) {
2760 1
                throw MappingException::duplicateColumnName($this->name, $columnDef['name']);
2761
            }
2762
2763 127
            if ( ! isset($columnDef['fieldName'])) {
2764 123
                $columnDef['fieldName'] = $columnDef['name'];
2765
            }
2766
2767 127
            if ( ! isset($columnDef['type'])) {
2768 2
                $columnDef['type'] = "string";
2769
            }
2770
2771 127
            if (in_array($columnDef['type'], ["boolean", "array", "object", "datetime", "time", "date"])) {
2772
                throw MappingException::invalidDiscriminatorColumnType($this->name, $columnDef['type']);
2773
            }
2774
2775 127
            $this->discriminatorColumn = $columnDef;
2776
        }
2777 188
    }
2778
2779
    /**
2780
     * Sets the discriminator values used by this class.
2781
     * Used for JOINED and SINGLE_TABLE inheritance mapping strategies.
2782
     *
2783
     * @param array $map
2784
     *
2785
     * @return void
2786
     */
2787 182
    public function setDiscriminatorMap(array $map)
2788
    {
2789 182
        foreach ($map as $value => $className) {
2790 122
            $this->addDiscriminatorMapClass($value, $className);
2791
        }
2792 182
    }
2793
2794
    /**
2795
     * Adds one entry of the discriminator map with a new class and corresponding name.
2796
     *
2797
     * @param string $name
2798
     * @param string $className
2799
     *
2800
     * @return void
2801
     *
2802
     * @throws MappingException
2803
     */
2804 123
    public function addDiscriminatorMapClass($name, $className)
2805
    {
2806 123
        $className = $this->fullyQualifiedClassName($className);
2807 123
        $className = ltrim($className, '\\');
2808
2809 123
        $this->discriminatorMap[$name] = $className;
2810
2811 123
        if ($this->name === $className) {
2812 94
            $this->discriminatorValue = $name;
2813
2814 94
            return;
2815
        }
2816
2817 122
        if ( ! (class_exists($className) || interface_exists($className))) {
2818
            throw MappingException::invalidClassInDiscriminatorMap($className, $this->name);
2819
        }
2820
2821 122
        if (is_subclass_of($className, $this->name) && ! in_array($className, $this->subClasses)) {
2822 112
            $this->subClasses[] = $className;
2823
        }
2824 122
    }
2825
2826
    /**
2827
     * Checks whether the class has a named query with the given query name.
2828
     *
2829
     * @param string $queryName
2830
     *
2831
     * @return boolean
2832
     */
2833 1
    public function hasNamedQuery($queryName)
2834
    {
2835 1
        return isset($this->namedQueries[$queryName]);
2836
    }
2837
2838
    /**
2839
     * Checks whether the class has a named native query with the given query name.
2840
     *
2841
     * @param string $queryName
2842
     *
2843
     * @return boolean
2844
     */
2845 1
    public function hasNamedNativeQuery($queryName)
2846
    {
2847 1
        return isset($this->namedNativeQueries[$queryName]);
2848
    }
2849
2850
    /**
2851
     * Checks whether the class has a named native query with the given query name.
2852
     *
2853
     * @param string $name
2854
     *
2855
     * @return boolean
2856
     */
2857 1
    public function hasSqlResultSetMapping($name)
2858
    {
2859 1
        return isset($this->sqlResultSetMappings[$name]);
2860
    }
2861
2862
    /**
2863
     * {@inheritDoc}
2864
     */
2865 340
    public function hasAssociation($fieldName)
2866
    {
2867 340
        return isset($this->associationMappings[$fieldName]);
2868
    }
2869
2870
    /**
2871
     * {@inheritDoc}
2872
     */
2873 1
    public function isSingleValuedAssociation($fieldName)
2874
    {
2875 1
        return isset($this->associationMappings[$fieldName])
2876 1
            && ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2877
    }
2878
2879
    /**
2880
     * {@inheritDoc}
2881
     */
2882 1108
    public function isCollectionValuedAssociation($fieldName)
2883
    {
2884 1108
        return isset($this->associationMappings[$fieldName])
2885 1108
            && ! ($this->associationMappings[$fieldName]['type'] & self::TO_ONE);
2886
    }
2887
2888
    /**
2889
     * Is this an association that only has a single join column?
2890
     *
2891
     * @param string $fieldName
2892
     *
2893
     * @return bool
2894
     */
2895 99
    public function isAssociationWithSingleJoinColumn($fieldName)
2896
    {
2897 99
        return isset($this->associationMappings[$fieldName])
2898 99
            && isset($this->associationMappings[$fieldName]['joinColumns'][0])
2899 99
            && ! isset($this->associationMappings[$fieldName]['joinColumns'][1]);
2900
    }
2901
2902
    /**
2903
     * Returns the single association join column (if any).
2904
     *
2905
     * @param string $fieldName
2906
     *
2907
     * @return string
2908
     *
2909
     * @throws MappingException
2910
     */
2911 10
    public function getSingleAssociationJoinColumnName($fieldName)
2912
    {
2913 10
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2914
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2915
        }
2916
2917 10
        return $this->associationMappings[$fieldName]['joinColumns'][0]['name'];
2918
    }
2919
2920
    /**
2921
     * Returns the single association referenced join column name (if any).
2922
     *
2923
     * @param string $fieldName
2924
     *
2925
     * @return string
2926
     *
2927
     * @throws MappingException
2928
     */
2929 10
    public function getSingleAssociationReferencedJoinColumnName($fieldName)
2930
    {
2931 10
        if ( ! $this->isAssociationWithSingleJoinColumn($fieldName)) {
2932
            throw MappingException::noSingleAssociationJoinColumnFound($this->name, $fieldName);
2933
        }
2934
2935 10
        return $this->associationMappings[$fieldName]['joinColumns'][0]['referencedColumnName'];
2936
    }
2937
2938
    /**
2939
     * Used to retrieve a fieldname for either field or association from a given column.
2940
     *
2941
     * This method is used in foreign-key as primary-key contexts.
2942
     *
2943
     * @param string $columnName
2944
     *
2945
     * @return string
2946
     *
2947
     * @throws MappingException
2948
     */
2949 674
    public function getFieldForColumn($columnName)
2950
    {
2951 674
        if (isset($this->fieldNames[$columnName])) {
2952 652
            return $this->fieldNames[$columnName];
2953
        }
2954
2955 98
        foreach ($this->associationMappings as $assocName => $mapping) {
2956 98
            if ($this->isAssociationWithSingleJoinColumn($assocName) &&
2957 98
                $this->associationMappings[$assocName]['joinColumns'][0]['name'] == $columnName) {
2958
2959 98
                return $assocName;
2960
            }
2961
        }
2962
2963
        throw MappingException::noFieldNameFoundForColumn($this->name, $columnName);
2964
    }
2965
2966
    /**
2967
     * Sets the ID generator used to generate IDs for instances of this class.
2968
     *
2969
     * @param \Doctrine\ORM\Id\AbstractIdGenerator $generator
2970
     *
2971
     * @return void
2972
     */
2973 497
    public function setIdGenerator($generator)
2974
    {
2975 497
        $this->idGenerator = $generator;
2976 497
    }
2977
2978
    /**
2979
     * Sets definition.
2980
     *
2981
     * @param array $definition
2982
     *
2983
     * @return void
2984
     */
2985 13
    public function setCustomGeneratorDefinition(array $definition)
2986
    {
2987 13
        $this->customGeneratorDefinition = $definition;
2988 13
    }
2989
2990
    /**
2991
     * Sets the definition of the sequence ID generator for this class.
2992
     *
2993
     * The definition must have the following structure:
2994
     * <code>
2995
     * array(
2996
     *     'sequenceName'   => 'name',
2997
     *     'allocationSize' => 20,
2998
     *     'initialValue'   => 1
2999
     *     'quoted'         => 1
3000
     * )
3001
     * </code>
3002
     *
3003
     * @param array $definition
3004
     *
3005
     * @return void
3006
     *
3007
     * @throws MappingException
3008
     */
3009 30
    public function setSequenceGeneratorDefinition(array $definition)
3010
    {
3011 30
        if ( ! isset($definition['sequenceName']) || trim($definition['sequenceName']) === '') {
3012 1
            throw MappingException::missingSequenceName($this->name);
3013
        }
3014
3015 29
        if ($definition['sequenceName'][0] == '`') {
3016 1
            $definition['sequenceName']   = trim($definition['sequenceName'], '`');
0 ignored issues
show
Bug introduced by
$definition['sequenceName'] of type array is incompatible with the type string expected by parameter $str of trim(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3016
            $definition['sequenceName']   = trim(/** @scrutinizer ignore-type */ $definition['sequenceName'], '`');
Loading history...
3017 1
            $definition['quoted'] = true;
3018
        }
3019
3020 29
        if ( ! isset($definition['allocationSize']) || trim($definition['allocationSize']) === '') {
3021 2
            $definition['allocationSize'] = '1';
3022
        }
3023
3024 29
        if ( ! isset($definition['initialValue']) || trim($definition['initialValue']) === '') {
3025 2
            $definition['initialValue'] = '1';
3026
        }
3027
3028 29
        $this->sequenceGeneratorDefinition = $definition;
3029 29
    }
3030
3031
    /**
3032
     * Sets the version field mapping used for versioning. Sets the default
3033
     * value to use depending on the column type.
3034
     *
3035
     * @param array $mapping The version field mapping array.
3036
     *
3037
     * @return void
3038
     *
3039
     * @throws MappingException
3040
     */
3041 35
    public function setVersionMapping(array &$mapping)
3042
    {
3043 35
        $this->isVersioned = true;
3044 35
        $this->versionField = $mapping['fieldName'];
3045
3046 35
        if ( ! isset($mapping['default'])) {
3047 35
            if (in_array($mapping['type'], ['integer', 'bigint', 'smallint'])) {
3048 33
                $mapping['default'] = 1;
3049 3
            } else if ($mapping['type'] == 'datetime') {
3050 2
                $mapping['default'] = 'CURRENT_TIMESTAMP';
3051
            } else {
3052 1
                throw MappingException::unsupportedOptimisticLockingType($this->name, $mapping['fieldName'], $mapping['type']);
3053
            }
3054
        }
3055 34
    }
3056
3057
    /**
3058
     * Sets whether this class is to be versioned for optimistic locking.
3059
     *
3060
     * @param boolean $bool
3061
     *
3062
     * @return void
3063
     */
3064 147
    public function setVersioned($bool)
3065
    {
3066 147
        $this->isVersioned = $bool;
3067 147
    }
3068
3069
    /**
3070
     * Sets the name of the field that is to be used for versioning if this class is
3071
     * versioned for optimistic locking.
3072
     *
3073
     * @param string $versionField
3074
     *
3075
     * @return void
3076
     */
3077 147
    public function setVersionField($versionField)
3078
    {
3079 147
        $this->versionField = $versionField;
3080 147
    }
3081
3082
    /**
3083
     * Marks this class as read only, no change tracking is applied to it.
3084
     *
3085
     * @return void
3086
     */
3087 3
    public function markReadOnly()
3088
    {
3089 3
        $this->isReadOnly = true;
3090 3
    }
3091
3092
    /**
3093
     * {@inheritDoc}
3094
     */
3095
    public function getFieldNames()
3096
    {
3097
        return array_keys($this->fieldMappings);
3098
    }
3099
3100
    /**
3101
     * {@inheritDoc}
3102
     */
3103
    public function getAssociationNames()
3104
    {
3105
        return array_keys($this->associationMappings);
3106
    }
3107
3108
    /**
3109
     * {@inheritDoc}
3110
     *
3111
     * @throws InvalidArgumentException
3112
     */
3113 1
    public function getAssociationTargetClass($assocName)
3114
    {
3115 1
        if ( ! isset($this->associationMappings[$assocName])) {
3116
            throw new InvalidArgumentException("Association name expected, '" . $assocName ."' is not an association.");
3117
        }
3118
3119 1
        return $this->associationMappings[$assocName]['targetEntity'];
3120
    }
3121
3122
    /**
3123
     * {@inheritDoc}
3124
     */
3125 817
    public function getName()
3126
    {
3127 817
        return $this->name;
3128
    }
3129
3130
    /**
3131
     * Gets the (possibly quoted) identifier column names for safe use in an SQL statement.
3132
     *
3133
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3134
     *
3135
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3136
     *
3137
     * @return array
3138
     */
3139
    public function getQuotedIdentifierColumnNames($platform)
3140
    {
3141
        $quotedColumnNames = [];
3142
3143
        foreach ($this->identifier as $idProperty) {
3144
            if (isset($this->fieldMappings[$idProperty])) {
3145
                $quotedColumnNames[] = isset($this->fieldMappings[$idProperty]['quoted'])
3146
                    ? $platform->quoteIdentifier($this->fieldMappings[$idProperty]['columnName'])
3147
                    : $this->fieldMappings[$idProperty]['columnName'];
3148
3149
                continue;
3150
            }
3151
3152
            // Association defined as Id field
3153
            $joinColumns            = $this->associationMappings[$idProperty]['joinColumns'];
3154
            $assocQuotedColumnNames = array_map(
3155
                function ($joinColumn) use ($platform) {
3156
                    return isset($joinColumn['quoted'])
3157
                        ? $platform->quoteIdentifier($joinColumn['name'])
3158
                        : $joinColumn['name'];
3159
                },
3160
                $joinColumns
3161
            );
3162
3163
            $quotedColumnNames = array_merge($quotedColumnNames, $assocQuotedColumnNames);
3164
        }
3165
3166
        return $quotedColumnNames;
3167
    }
3168
3169
    /**
3170
     * Gets the (possibly quoted) column name of a mapped field for safe use  in an SQL statement.
3171
     *
3172
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3173
     *
3174
     * @param string                                    $field
3175
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3176
     *
3177
     * @return string
3178
     */
3179
    public function getQuotedColumnName($field, $platform)
3180
    {
3181
        return isset($this->fieldMappings[$field]['quoted'])
3182
            ? $platform->quoteIdentifier($this->fieldMappings[$field]['columnName'])
3183
            : $this->fieldMappings[$field]['columnName'];
3184
    }
3185
3186
    /**
3187
     * Gets the (possibly quoted) primary table name of this class for safe use in an SQL statement.
3188
     *
3189
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3190
     *
3191
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3192
     *
3193
     * @return string
3194
     */
3195
    public function getQuotedTableName($platform)
3196
    {
3197
        return isset($this->table['quoted'])
3198
            ? $platform->quoteIdentifier($this->table['name'])
3199
            : $this->table['name'];
3200
    }
3201
3202
    /**
3203
     * Gets the (possibly quoted) name of the join table.
3204
     *
3205
     * @deprecated Deprecated since version 2.3 in favor of \Doctrine\ORM\Mapping\QuoteStrategy
3206
     *
3207
     * @param array                                     $assoc
3208
     * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
3209
     *
3210
     * @return string
3211
     */
3212
    public function getQuotedJoinTableName(array $assoc, $platform)
3213
    {
3214
        return isset($assoc['joinTable']['quoted'])
3215
            ? $platform->quoteIdentifier($assoc['joinTable']['name'])
3216
            : $assoc['joinTable']['name'];
3217
    }
3218
3219
    /**
3220
     * {@inheritDoc}
3221
     */
3222 12
    public function isAssociationInverseSide($fieldName)
3223
    {
3224 12
        return isset($this->associationMappings[$fieldName])
3225 12
            && ! $this->associationMappings[$fieldName]['isOwningSide'];
3226
    }
3227
3228
    /**
3229
     * {@inheritDoc}
3230
     */
3231
    public function getAssociationMappedByTargetField($fieldName)
3232
    {
3233
        return $this->associationMappings[$fieldName]['mappedBy'];
3234
    }
3235
3236
    /**
3237
     * @param string $targetClass
3238
     *
3239
     * @return array
3240
     */
3241 2
    public function getAssociationsByTargetClass($targetClass)
3242
    {
3243 2
        $relations = [];
3244
3245 2
        foreach ($this->associationMappings as $mapping) {
3246 2
            if ($mapping['targetEntity'] == $targetClass) {
3247 2
                $relations[$mapping['fieldName']] = $mapping;
3248
            }
3249
        }
3250
3251 2
        return $relations;
3252
    }
3253
3254
    /**
3255
     * @param  string|null $className
3256
     *
3257
     * @return string|null null if the input value is null
3258
     */
3259 548
    public function fullyQualifiedClassName($className)
3260
    {
3261 548
        if (empty($className)) {
3262 48
            return $className;
3263
        }
3264
3265 532
        if ($className !== null && strpos($className, '\\') === false && $this->namespace) {
3266 409
            return $this->namespace . '\\' . $className;
3267
        }
3268
3269 263
        return $className;
3270
    }
3271
3272
    /**
3273
     * @param string $name
3274
     *
3275
     * @return mixed
3276
     */
3277 2
    public function getMetadataValue($name)
3278
    {
3279
3280 2
        if (isset($this->$name)) {
3281 2
            return $this->$name;
3282
        }
3283
3284
        return null;
3285
    }
3286
3287
    /**
3288
     * Map Embedded Class
3289
     *
3290
     * @param array $mapping
3291
     *
3292
     * @throws MappingException
3293
     * @return void
3294
     */
3295 32
    public function mapEmbedded(array $mapping)
3296
    {
3297 32
        $this->assertFieldNotMapped($mapping['fieldName']);
3298
3299 32
        $this->embeddedClasses[$mapping['fieldName']] = [
3300 32
            'class' => $this->fullyQualifiedClassName($mapping['class']),
3301 32
            'columnPrefix' => $mapping['columnPrefix'],
3302 32
            'nullable' => $mapping['nullable'] ?? null,
3303 32
            'declaredField' => $mapping['declaredField'] ?? null,
3304 32
            'originalField' => $mapping['originalField'] ?? null,
3305
        ];
3306 32
    }
3307
3308
    /**
3309
     * Inline the embeddable class
3310
     *
3311
     * @param string            $property
3312
     * @param ClassMetadataInfo $embeddable
3313
     */
3314 12
    public function inlineEmbeddable($property, ClassMetadataInfo $embeddable)
3315
    {
3316 12
        foreach ($embeddable->fieldMappings as $fieldMapping) {
3317 12
            $fieldMapping['originalClass'] = $fieldMapping['originalClass'] ?? $embeddable->name;
3318 12
            $fieldMapping['declaredField'] = isset($fieldMapping['declaredField'])
3319 4
                ? $property . '.' . $fieldMapping['declaredField']
3320 12
                : $property;
3321 12
            $fieldMapping['originalField'] = $fieldMapping['originalField'] ?? $fieldMapping['fieldName'];
3322 12
            $fieldMapping['fieldName'] = $property . "." . $fieldMapping['fieldName'];
3323
3324 12
            if ($this->embeddedClasses[$property]['nullable'] === true) {
3325 1
                $fieldMapping['nullable'] = $this->embeddedClasses[$property]['nullable'];
3326
            }
3327
3328 12
            if (! empty($this->embeddedClasses[$property]['columnPrefix'])) {
3329 2
                $fieldMapping['columnName'] = $this->embeddedClasses[$property]['columnPrefix'] . $fieldMapping['columnName'];
3330 11
            } elseif ($this->embeddedClasses[$property]['columnPrefix'] !== false) {
3331 8
                $fieldMapping['columnName'] = $this->namingStrategy
3332 8
                    ->embeddedFieldToColumnName(
3333 8
                        $property,
3334 8
                        $fieldMapping['columnName'],
3335 8
                        $this->reflClass->name,
3336 8
                        $embeddable->reflClass->name
3337
                    );
3338
            }
3339
3340 12
            $this->mapField($fieldMapping);
3341
        }
3342 12
    }
3343
3344
    /**
3345
     * @param string $fieldName
3346
     * @throws MappingException
3347
     */
3348 677
    private function assertFieldNotMapped($fieldName)
3349
    {
3350 677
        if (isset($this->fieldMappings[$fieldName]) ||
3351 677
            isset($this->associationMappings[$fieldName]) ||
3352 677
            isset($this->embeddedClasses[$fieldName])) {
3353
3354 2
            throw MappingException::duplicateFieldMapping($this->name, $fieldName);
3355
        }
3356 677
    }
3357
3358
    /**
3359
     * Gets the sequence name based on class metadata.
3360
     *
3361
     * @param AbstractPlatform $platform
3362
     * @return string
3363
     *
3364
     * @todo Sequence names should be computed in DBAL depending on the platform
3365
     */
3366 3
    public function getSequenceName(AbstractPlatform $platform)
3367
    {
3368 3
        $sequencePrefix = $this->getSequencePrefix($platform);
3369 3
        $columnName     = $this->getSingleIdentifierColumnName();
3370 3
        $sequenceName   = $sequencePrefix . '_' . $columnName . '_seq';
3371
3372 3
        return $sequenceName;
3373
    }
3374
3375
    /**
3376
     * Gets the sequence name prefix based on class metadata.
3377
     *
3378
     * @param AbstractPlatform $platform
3379
     * @return string
3380
     *
3381
     * @todo Sequence names should be computed in DBAL depending on the platform
3382
     */
3383 3
    public function getSequencePrefix(AbstractPlatform $platform)
3384
    {
3385 3
        $tableName      = $this->getTableName();
3386 3
        $sequencePrefix = $tableName;
3387
3388
        // Prepend the schema name to the table name if there is one
3389 3
        if ($schemaName = $this->getSchemaName()) {
3390 3
            $sequencePrefix = $schemaName . '.' . $tableName;
3391
3392 3
            if ( ! $platform->supportsSchemas() && $platform->canEmulateSchemas()) {
3393 3
                $sequencePrefix = $schemaName . '__' . $tableName;
3394
            }
3395
        }
3396
3397 3
        return $sequencePrefix;
3398
    }
3399
3400
    /**
3401
     * @param array $mapping
3402
     */
3403 250
    private function assertMappingOrderBy(array $mapping)
3404
    {
3405 250
        if (isset($mapping['orderBy']) && !is_array($mapping['orderBy'])) {
3406
            throw new InvalidArgumentException("'orderBy' is expected to be an array, not " . gettype($mapping['orderBy']));
3407
        }
3408 250
    }
3409
}
3410