Completed
Pull Request — master (#7825)
by
unknown
61:51
created

JoinedSubclassPersister   F

Complexity

Total Complexity 90

Size/Duplication

Total Lines 520
Duplicated Lines 0 %

Test Coverage

Coverage 95.62%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 252
dl 0
loc 520
ccs 240
cts 251
cp 0.9562
rs 2
c 6
b 0
f 0
wmc 90

9 Methods

Rating   Name   Duplication   Size   Complexity  
D getSelectColumnsSQL() 0 93 19
D getInsertColumnList() 0 68 20
F insert() 0 92 16
B getJoinSql() 0 43 6
B getSelectSQL() 0 61 10
A delete() 0 30 4
A getLockTablesSql() 0 27 4
A getCountSQL() 0 24 5
A update() 0 31 6

How to fix   Complexity   

Complex Class

Complex classes like JoinedSubclassPersister 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 JoinedSubclassPersister, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Persisters\Entity;
6
7
use Doctrine\Common\Collections\Criteria;
8
use Doctrine\DBAL\LockMode;
9
use Doctrine\DBAL\Statement;
10
use Doctrine\DBAL\Types\Type;
11
use Doctrine\ORM\Mapping\AssociationMetadata;
12
use Doctrine\ORM\Mapping\FieldMetadata;
13
use Doctrine\ORM\Mapping\GeneratorType;
14
use Doctrine\ORM\Mapping\JoinColumnMetadata;
15
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
16
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
17
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
18
use Doctrine\ORM\Mapping\TransientMetadata;
19
use Doctrine\ORM\Utility\PersisterHelper;
20
use function array_combine;
21
use function array_keys;
22
use function implode;
23
use function is_array;
24
25
/**
26
 * The joined subclass persister maps a single entity instance to several tables in the
27
 * database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
28
 *
29
 * @see https://martinfowler.com/eaaCatalog/classTableInheritance.html
30
 */
31
class JoinedSubclassPersister extends AbstractEntityInheritancePersister
32
{
33
    /**
34
     * {@inheritdoc}
35 295
     */
36
    public function insert($entity)
37 295
    {
38 289
        $rootClass      = ! $this->class->isRootEntity()
39 295
            ? $this->em->getClassMetadata($this->class->getRootClassName())
40 295
            : $this->class;
41
        $generationPlan = $this->class->getValueGenerationPlan();
42
43 295
        // Prepare statement for the root table
44 295
        $rootPersister = $this->em->getUnitOfWork()->getEntityPersister($rootClass->getClassName());
0 ignored issues
show
Bug introduced by
The method getClassName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

44
        $rootPersister = $this->em->getUnitOfWork()->getEntityPersister($rootClass->/** @scrutinizer ignore-call */ getClassName());

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...
45 295
        $rootTableName = $rootClass->getTableName();
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

45
        /** @scrutinizer ignore-call */ 
46
        $rootTableName = $rootClass->getTableName();

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...
46
        $rootTableStmt = $this->conn->prepare($rootPersister->getInsertSQL());
0 ignored issues
show
Bug introduced by
The method getInsertSQL() does not exist on Doctrine\ORM\Persisters\Entity\EntityPersister. It seems like you code against a sub-type of said class. However, the method does not exist in Doctrine\ORM\Cache\Persi...y\CachedEntityPersister. Are you sure you never get one of those? ( Ignorable by Annotation )

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

46
        $rootTableStmt = $this->conn->prepare($rootPersister->/** @scrutinizer ignore-call */ getInsertSQL());
Loading history...
47
48 295
        // Prepare statements for sub tables.
49
        $subTableStmts = [];
50 295
51 289
        if ($rootClass !== $this->class) {
52
            $subTableStmts[$this->class->getTableName()] = $this->conn->prepare($this->getInsertSQL());
53
        }
54 295
55
        $parentClass = $this->class;
56 295
57 289
        while (($parentClass = $parentClass->getParent()) !== null) {
58
            if (! $parentClass->isMappedSuperclass) {
59 289
                $parentTableName = $parentClass->getTableName();
0 ignored issues
show
Bug introduced by
The method getTableName() does not exist on Doctrine\ORM\Mapping\ComponentMetadata. It seems like you code against a sub-type of Doctrine\ORM\Mapping\ComponentMetadata such as Doctrine\ORM\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

59
                /** @scrutinizer ignore-call */ 
60
                $parentTableName = $parentClass->getTableName();
Loading history...
60 170
61
                if ($parentClass !== $rootClass) {
62 170
                    $parentPersister = $this->em->getUnitOfWork()->getEntityPersister($parentClass->getClassName());
63
64
                    $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL());
65
                }
66
            }
67
        }
68
69 295
        // Execute all inserts. For each entity:
70
        // 1) Insert on root table
71
        // 2) Insert on sub tables
72 295
        $insertData = $this->prepareInsertData($entity);
73
74 295
        // Execute insert on root table
75 295
        $paramIndex = 1;
76
77 295
        foreach ($insertData[$rootTableName] as $columnName => $value) {
78
            $type = $this->columns[$columnName]->getType();
79
80 295
            $rootTableStmt->bindValue($paramIndex++, $value, $type);
81
        }
82 295
83 291
        $rootTableStmt->execute();
84 291
85
        if ($generationPlan->containsDeferred()) {
86 4
            $generationPlan->executeDeferred($this->em, $entity);
87
            $id = $this->getIdentifier($entity);
88
        } else {
89 295
            $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
90 9
        }
91
92
        if ($this->class->isVersioned()) {
93
            $this->assignDefaultVersionValue($entity, $id);
94
        }
95 295
96
        // Execute inserts on subtables.
97 289
        // The order doesn't matter because all child tables link to the root table via FK.
98 289
        foreach ($subTableStmts as $tableName => $stmt) {
99
            /** @var Statement $stmt */
100 289
            $paramIndex = 1;
101 289
            $data       = $insertData[$tableName] ?? [];
102
103 289
            foreach ((array) $id as $idName => $idVal) {
104 289
                $type = Type::getType('string');
105
106
                if (isset($this->columns[$idName])) {
107 289
                    $type = $this->columns[$idName]->getType();
108
                }
109
110 289
                $stmt->bindValue($paramIndex++, $idVal, $type);
111 224
            }
112 224
113
            foreach ($data as $columnName => $value) {
114 224
                if (! is_array($id) || ! isset($id[$columnName])) {
115
                    $type = $this->columns[$columnName]->getType();
116
117
                    $stmt->bindValue($paramIndex++, $value, $type);
118 289
                }
119
            }
120
121 295
            $stmt->execute();
122
        }
123 295
124 289
        $rootTableStmt->closeCursor();
125
126 295
        foreach ($subTableStmts as $stmt) {
127
            $stmt->closeCursor();
128
        }
129
    }
130
131 30
    /**
132
     * {@inheritdoc}
133 30
     */
134
    public function update($entity)
135 30
    {
136
        $updateData = $this->prepareUpdateData($entity);
137
138
        if (! $updateData) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $updateData of type array<mixed,mixed> 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...
139 30
            return;
140
        }
141 30
142 30
        $isVersioned = $this->class->isVersioned();
143
144 30
        foreach ($updateData as $tableName => $data) {
145
            $versioned = $isVersioned && $this->class->versionProperty->getTableName() === $tableName;
146
147
            $this->updateTable($entity, $this->platform->quoteIdentifier($tableName), $data, $versioned);
148
        }
149 29
150 5
        // Make sure the table with the version column is updated even if no columns on that
151 5
        // table were affected.
152
        if ($isVersioned) {
153 5
            $versionedClass = $this->class->versionProperty->getDeclaringClass();
0 ignored issues
show
Bug introduced by
The method getDeclaringClass() 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

153
            /** @scrutinizer ignore-call */ 
154
            $versionedClass = $this->class->versionProperty->getDeclaringClass();

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...
154 2
            $versionedTable = $versionedClass->getTableName();
155
156 2
            if (! isset($updateData[$versionedTable])) {
157
                $tableName = $versionedClass->table->getQuotedQualifiedName($this->platform);
158
159 4
                $this->updateTable($entity, $tableName, [], true);
160
            }
161 4
162
            $identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
163 28
164
            $this->assignDefaultVersionValue($entity, $identifiers);
165
        }
166
    }
167
168 4
    /**
169
     * {@inheritdoc}
170 4
     */
171 4
    public function delete($entity)
172
    {
173 4
        $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
174
        $id         = array_combine(array_keys($this->class->getIdentifierColumns($this->em)), $identifier);
175
176
        $this->deleteJoinTableRecords($identifier);
177 4
178
        // If the database platform supports FKs, just
179
        // delete the row from the root table. Cascades do the rest.
180
        if ($this->platform->supportsForeignKeyConstraints()) {
181
            $rootClass = $this->em->getClassMetadata($this->class->getRootClassName());
182
            $rootTable = $rootClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
183
184
            return (bool) $this->conn->delete($rootTable, $id);
0 ignored issues
show
Bug introduced by
It seems like $id can also be of type false; however, parameter $identifier of Doctrine\DBAL\Connection::delete() does only seem to accept array, 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

184
            return (bool) $this->conn->delete($rootTable, /** @scrutinizer ignore-type */ $id);
Loading history...
185 4
        }
186 4
187 4
        // Delete from all tables individually, starting from this class' table up to the root table.
188
        $rootTable    = $this->class->table->getQuotedQualifiedName($this->platform);
189 4
        $affectedRows = $this->conn->delete($rootTable, $id);
190 3
        $parentClass  = $this->class;
191
192 3
        while (($parentClass = $parentClass->getParent()) !== null) {
193
            if (! $parentClass->isMappedSuperclass) {
194
                $parentTable = $parentClass->table->getQuotedQualifiedName($this->platform);
195 4
196
                $this->conn->delete($parentTable, $id);
197
            }
198
        }
199
200
        return (bool) $affectedRows;
201 68
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function getSelectSQL(
207
        $criteria,
208
        ?AssociationMetadata $association = null,
209 68
        $lockMode = null,
210
        $limit = null,
211 68
        $offset = null,
212 68
        array $orderBy = []
213
    ) {
214 68
        $this->switchPersisterContext($offset, $limit);
215 2
216
        $baseTableAlias = $this->getSQLTableAlias($this->class->getTableName());
217
        $joinSql        = $this->getJoinSql($baseTableAlias);
218 68
219 1
        if ($association instanceof ManyToManyAssociationMetadata) {
220
            $joinSql .= $this->getSelectManyToManyJoinSQL($association);
221
        }
222 68
223 68
        if ($association instanceof ToManyAssociationMetadata && $association->getOrderBy()) {
224
            $orderBy = $association->getOrderBy();
225 68
        }
226
227
        $orderBySql   = $this->getOrderBySQL($orderBy, $baseTableAlias);
228 68
        $conditionSql = $criteria instanceof Criteria
229 68
            ? $this->getSelectConditionCriteriaSQL($criteria)
230 68
            : $this->getSelectConditionSQL($criteria, $association);
231
232 68
        // If the current class in the root entity, add the filters
233 4
        $rootClass  = $this->em->getClassMetadata($this->class->getRootClassName());
234 2
        $tableAlias = $this->getSQLTableAlias($rootClass->getTableName());
235 4
        $filterSql  = $this->generateFilterConditionSQL($rootClass, $tableAlias);
236
237
        if ($filterSql) {
238 68
            $conditionSql .= $conditionSql
239
                ? ' AND ' . $filterSql
240
                : $filterSql;
241 68
        }
242
243
        $lockSql = '';
244
245 68
        switch ($lockMode) {
246
            case LockMode::PESSIMISTIC_READ:
247
                $lockSql = ' ' . $this->platform->getReadLockSQL();
248
                break;
249
250 68
            case LockMode::PESSIMISTIC_WRITE:
251 68
                $lockSql = ' ' . $this->platform->getWriteLockSQL();
252 68
                break;
253 68
        }
254 68
255 68
        $tableName  = $this->class->table->getQuotedQualifiedName($this->platform);
256 68
        $from       = ' FROM ' . $tableName . ' ' . $baseTableAlias;
257 68
        $where      = $conditionSql !== '' ? ' WHERE ' . $conditionSql : '';
258 68
        $lock       = $this->platform->appendLockHint($from, $lockMode);
259 68
        $columnList = $this->getSelectColumnsSQL();
260
        $query      = 'SELECT ' . $columnList
261 68
                    . $lock
262
                    . $joinSql
263
                    . $where
264
                    . $orderBySql;
265
266
        return $this->platform->modifyLimitQuery($query, $limit, $offset ?? 0) . $lockSql;
267 6
    }
268
269 6
    /**
270 6
     * {@inheritDoc}
271 6
     */
272
    public function getCountSQL($criteria = [])
273 6
    {
274 1
        $tableName      = $this->class->table->getQuotedQualifiedName($this->platform);
275 6
        $baseTableAlias = $this->getSQLTableAlias($this->class->getTableName());
276
        $joinSql        = $this->getJoinSql($baseTableAlias);
277 6
278 6
        $conditionSql = $criteria instanceof Criteria
279 6
            ? $this->getSelectConditionCriteriaSQL($criteria)
280
            : $this->getSelectConditionSQL($criteria);
281 6
282 1
        $rootClass  = $this->em->getClassMetadata($this->class->getRootClassName());
283 1
        $tableAlias = $this->getSQLTableAlias($rootClass->getTableName());
284 1
        $filterSql  = $this->generateFilterConditionSQL($rootClass, $tableAlias);
285
286
        if ($filterSql !== '') {
287
            $conditionSql = $conditionSql
288 6
                ? $conditionSql . ' AND ' . $filterSql
289 6
                : $filterSql;
290 6
        }
291
292
        return 'SELECT COUNT(*) '
293
            . 'FROM ' . $tableName . ' ' . $baseTableAlias
294
            . $joinSql
295
            . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql);
296 6
    }
297
298 6
    /**
299 6
     * {@inheritdoc}
300 6
     */
301
    protected function getLockTablesSql($lockMode)
302
    {
303 6
        $joinSql           = '';
304
        $identifierColumns = $this->class->getIdentifierColumns($this->em);
305 6
        $baseTableAlias    = $this->getSQLTableAlias($this->class->getTableName());
306 5
307 5
        // INNER JOIN parent tables
308 5
        $parentClass = $this->class;
309 5
310
        while (($parentClass = $parentClass->getParent()) !== null) {
311 5
            if (! $parentClass->isMappedSuperclass) {
312 5
                $conditions = [];
313
                $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
314 5
                $tableAlias = $this->getSQLTableAlias($parentClass->getTableName());
315
                $joinSql   .= ' INNER JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
316
317 5
                foreach ($identifierColumns as $idColumn) {
318
                    $quotedColumnName = $this->platform->quoteIdentifier($idColumn->getColumnName());
319
320 6
                    $conditions[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
321
                }
322
323
                $joinSql .= implode(' AND ', $conditions);
324
            }
325
        }
326
327
        return parent::getLockTablesSql($lockMode) . $joinSql;
328 68
    }
329
330
    /**
331 68
     * Ensure this method is never called. This persister overrides getSelectEntitiesSQL directly.
332 12
     *
333
     * @return string
334
     */
335 68
    protected function getSelectColumnsSQL()
336
    {
337 68
        // Create the column list fragment only once
338
        if ($this->currentPersisterContext->selectColumnListSql !== null) {
339
            return $this->currentPersisterContext->selectColumnListSql;
340 68
        }
341 68
342 68
        $this->currentPersisterContext->rsm->addEntityResult($this->class->getClassName(), 'r');
343
344 68
        $columnList = [];
345
346
        // Add columns
347 56
        foreach ($this->class->getPropertiesIterator() as $fieldName => $property) {
348 44
            if ($property instanceof FieldMetadata) {
349
                $tableClass = $parentClass = $this->class;
350
                while ($parentClass !== $property->getDeclaringClass() && ($parentClass = $parentClass->getParent()) !== null) {
351 55
                    if (! $parentClass->isMappedSuperclass) {
352
                        $tableClass = $parentClass;
353 55
                    }
354
                }
355 55
                $columnList[] = $this->getSelectColumnSQL($fieldName, $tableClass);
356
357 55
                continue;
358
            }
359
360
            if (! ($property instanceof ToOneAssociationMetadata) || ! $property->isOwningSide()) {
361 55
                continue;
362
            }
363
364
            $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
365
366 68
            foreach ($property->getJoinColumns() as $joinColumn) {
367 68
                /** @var JoinColumnMetadata $joinColumn */
368 68
                $referencedColumnName = $joinColumn->getReferencedColumnName();
369 68
370 68
                if (! $joinColumn->getType()) {
371 68
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
372
                }
373 68
374 68
                $columnList[] = $this->getSelectJoinColumnSQL($joinColumn);
375
            }
376 68
        }
377
378
        // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#processSQLResult).
379 68
        $discrColumn      = $this->class->discriminatorColumn;
380 50
        $discrTableAlias  = $this->getSQLTableAlias($discrColumn->getTableName());
381
        $discrColumnName  = $discrColumn->getColumnName();
382
        $discrColumnType  = $discrColumn->getType();
383 50
        $resultColumnName = $this->platform->getSQLResultCasing($discrColumnName);
384 50
        $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
385 50
386
        $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName);
387
        $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType);
0 ignored issues
show
Bug introduced by
It seems like $discrColumnType can also be of type null; however, parameter $type of Doctrine\ORM\Query\Resul...apping::addMetaResult() does only seem to accept Doctrine\DBAL\Types\Type, 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

387
        $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, /** @scrutinizer ignore-type */ $discrColumnType);
Loading history...
388
389 46
        $columnList[] = $discrColumnType->convertToDatabaseValueSQL($discrTableAlias . '.' . $quotedColumnName, $this->platform);
390 46
391 46
        // sub tables
392
        foreach ($this->class->getSubClasses() as $subClassName) {
393 32
            $subClass = $this->em->getClassMetadata($subClassName);
394 32
395
            // Add columns
396 32
            foreach ($subClass->getPropertiesIterator() as $fieldName => $property) {
0 ignored issues
show
Bug introduced by
The method getPropertiesIterator() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

396
            foreach ($subClass->/** @scrutinizer ignore-call */ getPropertiesIterator() as $fieldName => $property) {

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...
397
                if ($subClass->isInheritedProperty($fieldName)) {
0 ignored issues
show
Bug introduced by
The method isInheritedProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

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

397
                if ($subClass->/** @scrutinizer ignore-call */ isInheritedProperty($fieldName)) {

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...
398 32
                    continue;
399
                }
400 32
401
                switch (true) {
402
                    case $property instanceof FieldMetadata:
403
                        $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass);
404 32
                        break;
405
406
                    case $property instanceof ToOneAssociationMetadata && $property->isOwningSide():
407 32
                        $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
408
409
                        foreach ($property->getJoinColumns() as $joinColumn) {
410
                            /** @var JoinColumnMetadata $joinColumn */
411
                            $referencedColumnName = $joinColumn->getReferencedColumnName();
412 68
413
                            if (! $joinColumn->getType()) {
414 68
                                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
415
                            }
416
417
                            $columnList[] = $this->getSelectJoinColumnSQL($joinColumn);
418
                        }
419
420 295
                        break;
421
                }
422
            }
423 295
        }
424 295
425 289
        $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
426 295
427
        return $this->currentPersisterContext->selectColumnListSql;
428 295
    }
429 289
430
    /**
431 289
     * {@inheritdoc}
432
     */
433
    protected function getInsertColumnList()
434 295
    {
435 295
        // Identifier columns must always come first in the column list of subclasses.
436 295
        $columns       = [];
437
        $parentColumns = $this->class->isRootEntity()
438 293
            ? []
439
            : $this->class->getIdentifierColumns($this->em);
440
441 295
        foreach ($parentColumns as $columnName => $column) {
442 265
            $columns[] = $columnName;
443 264
444
            $this->columns[$columnName] = $column;
445 264
        }
446
447 264
        foreach ($this->class->getPropertiesIterator() as $name => $property) {
448 264
            if (($property instanceof FieldMetadata && ($property->isVersioned() || $this->class->isInheritedProperty($name)))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($property instanceof Do...pping\TransientMetadata, Probably Intended Meaning: $property instanceof Doc...ping\TransientMetadata)
Loading history...
449
                || ($property instanceof AssociationMetadata && $this->class->isInheritedProperty($name)
450 264
                || $property instanceof TransientMetadata)
451 12
                /*|| isset($this->class->embeddedClasses[$name])*/) {
452
                continue;
453
            }
454 264
455
            if ($property instanceof AssociationMetadata) {
456 264
                if ($property->isOwningSide() && $property instanceof ToOneAssociationMetadata) {
457
                    $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
458
459
                    foreach ($property->getJoinColumns() as $joinColumn) {
460 265
                        /** @var JoinColumnMetadata $joinColumn */
461
                        $columnName           = $joinColumn->getColumnName();
462
                        $referencedColumnName = $joinColumn->getReferencedColumnName();
463 295
464 295
                        if (! $joinColumn->getType()) {
465 291
                            $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
466 295
                        }
467
468 233
                        $columns[] = $columnName;
469
470 233
                        $this->columns[$columnName] = $joinColumn;
471
                    }
472 233
                }
473
474
                continue;
475
            }
476
477 295
            if ($this->class->getClassName() !== $this->class->getRootClassName()
478 295
                || ! $this->class->getProperty($name)->hasValueGenerator()
0 ignored issues
show
Bug introduced by
The method hasValueGenerator() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

478
                || ! $this->class->getProperty($name)->/** @scrutinizer ignore-call */ hasValueGenerator()
Loading history...
479 295
                || $this->class->getProperty($name)->getValueGenerator()->getType() !== GeneratorType::IDENTITY
0 ignored issues
show
Bug introduced by
The method getValueGenerator() does not exist on Doctrine\ORM\Mapping\Property. Did you maybe mean getValue()? ( Ignorable by Annotation )

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

479
                || $this->class->getProperty($name)->/** @scrutinizer ignore-call */ getValueGenerator()->getType() !== GeneratorType::IDENTITY

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...
480
                || $this->class->identifier[0] !== $name
481 295
            ) {
482
                $columnName = $property->getColumnName();
0 ignored issues
show
Bug introduced by
The method getColumnName() does not exist on Doctrine\ORM\Mapping\Property. It seems like you code against a sub-type of Doctrine\ORM\Mapping\Property such as Doctrine\ORM\Mapping\FieldMetadata. ( Ignorable by Annotation )

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

482
                /** @scrutinizer ignore-call */ 
483
                $columnName = $property->getColumnName();
Loading history...
483 295
484
                $columns[] = $columnName;
485
486 295
                $this->columns[$columnName] = $property;
487
            }
488
        }
489
490
        // Add discriminator column if it is the topmost class.
491
        if ($this->class->isRootEntity()) {
492
            $discrColumn     = $this->class->discriminatorColumn;
493
            $discrColumnName = $discrColumn->getColumnName();
494 73
495
            $columns[] = $discrColumnName;
496 73
497 73
            $this->columns[$discrColumnName] = $discrColumn;
498
        }
499
500 73
        return $columns;
501
    }
502 73
503 49
    /**
504 49
     * @param string $baseTableAlias
505 49
     *
506 49
     * @return string
507
     */
508 49
    private function getJoinSql($baseTableAlias)
509 49
    {
510
        $joinSql           = '';
511 49
        $identifierColumns = $this->class->getIdentifierColumns($this->em);
512
513
        // INNER JOIN parent tables
514 49
        $parentClass = $this->class;
515
516
        while (($parentClass = $parentClass->getParent()) !== null) {
517
            if (! $parentClass->isMappedSuperclass) {
518 73
                $conditions = [];
519 52
                $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
520 52
                $tableAlias = $this->getSQLTableAlias($parentClass->getTableName());
521 52
                $joinSql   .= ' INNER JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
522 52
523 52
                foreach ($identifierColumns as $idColumn) {
524
                    $quotedColumnName = $this->platform->quoteIdentifier($idColumn->getColumnName());
525 52
526 52
                    $conditions[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
527
                }
528 52
529
                $joinSql .= implode(' AND ', $conditions);
530
            }
531 52
        }
532
533
        // OUTER JOIN sub tables
534 73
        foreach ($this->class->getSubClasses() as $subClassName) {
535
            $conditions = [];
536
            $subClass   = $this->em->getClassMetadata($subClassName);
537
            $tableName  = $subClass->table->getQuotedQualifiedName($this->platform);
0 ignored issues
show
Bug introduced by
Accessing table on the interface Doctrine\Common\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
538
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName());
539
            $joinSql   .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
540
541
            foreach ($identifierColumns as $idColumn) {
542
                $quotedColumnName = $this->platform->quoteIdentifier($idColumn->getColumnName());
543
544
                $conditions[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
545
            }
546
547
            $joinSql .= implode(' AND ', $conditions);
548
        }
549
550
        return $joinSql;
551
    }
552
}
553