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

JoinedSubclassPersister   F

Complexity

Total Complexity 89

Size/Duplication

Total Lines 519
Duplicated Lines 0 %

Test Coverage

Coverage 95.62%

Importance

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

9 Methods

Rating   Name   Duplication   Size   Complexity  
F insert() 0 92 16
A update() 0 31 6
D getSelectColumnsSQL() 0 93 19
D getInsertColumnList() 0 67 19
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

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\Utility\PersisterHelper;
19
use function array_combine;
20
use function array_keys;
21
use function implode;
22
use function is_array;
23
24
/**
25
 * The joined subclass persister maps a single entity instance to several tables in the
26
 * database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
27
 *
28
 * @see https://martinfowler.com/eaaCatalog/classTableInheritance.html
29
 */
30
class JoinedSubclassPersister extends AbstractEntityInheritancePersister
31
{
32
    /**
33
     * {@inheritdoc}
34
     */
35 295
    public function insert($entity)
36
    {
37 295
        $rootClass      = ! $this->class->isRootEntity()
38 289
            ? $this->em->getClassMetadata($this->class->getRootClassName())
39 295
            : $this->class;
40 295
        $generationPlan = $this->class->getValueGenerationPlan();
41
42
        // Prepare statement for the root table
43 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

43
        $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...
44 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

44
        /** @scrutinizer ignore-call */ 
45
        $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...
45 295
        $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

45
        $rootTableStmt = $this->conn->prepare($rootPersister->/** @scrutinizer ignore-call */ getInsertSQL());
Loading history...
46
47
        // Prepare statements for sub tables.
48 295
        $subTableStmts = [];
49
50 295
        if ($rootClass !== $this->class) {
51 289
            $subTableStmts[$this->class->getTableName()] = $this->conn->prepare($this->getInsertSQL());
52
        }
53
54 295
        $parentClass = $this->class;
55
56 295
        while (($parentClass = $parentClass->getParent()) !== null) {
57 289
            if (! $parentClass->isMappedSuperclass) {
58
                $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

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

152
            /** @scrutinizer ignore-call */ 
153
            $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...
153 5
            $versionedTable = $versionedClass->getTableName();
154 2
155
            if (! isset($updateData[$versionedTable])) {
156 2
                $tableName = $versionedClass->table->getQuotedQualifiedName($this->platform);
157
158
                $this->updateTable($entity, $tableName, [], true);
159 4
            }
160
161 4
            $identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
162
163 28
            $this->assignDefaultVersionValue($entity, $identifiers);
164
        }
165
    }
166
167
    /**
168 4
     * {@inheritdoc}
169
     */
170 4
    public function delete($entity)
171 4
    {
172
        $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
173 4
        $id         = array_combine(array_keys($this->class->getIdentifierColumns($this->em)), $identifier);
174
175
        $this->deleteJoinTableRecords($identifier);
176
177 4
        // If the database platform supports FKs, just
178
        // delete the row from the root table. Cascades do the rest.
179
        if ($this->platform->supportsForeignKeyConstraints()) {
180
            $rootClass = $this->em->getClassMetadata($this->class->getRootClassName());
181
            $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...
182
183
            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

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

386
        $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, /** @scrutinizer ignore-type */ $discrColumnType);
Loading history...
387
388
        $columnList[] = $discrColumnType->convertToDatabaseValueSQL($discrTableAlias . '.' . $quotedColumnName, $this->platform);
389 46
390 46
        // sub tables
391 46
        foreach ($this->class->getSubClasses() as $subClassName) {
392
            $subClass = $this->em->getClassMetadata($subClassName);
393 32
394 32
            // Add columns
395
            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

395
            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...
396 32
                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

396
                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...
397
                    continue;
398 32
                }
399
400 32
                switch (true) {
401
                    case $property instanceof FieldMetadata:
402
                        $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass);
403
                        break;
404 32
405
                    case $property instanceof ToOneAssociationMetadata && $property->isOwningSide():
406
                        $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
407 32
408
                        foreach ($property->getJoinColumns() as $joinColumn) {
409
                            /** @var JoinColumnMetadata $joinColumn */
410
                            $referencedColumnName = $joinColumn->getReferencedColumnName();
411
412 68
                            if (! $joinColumn->getType()) {
413
                                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
414 68
                            }
415
416
                            $columnList[] = $this->getSelectJoinColumnSQL($joinColumn);
417
                        }
418
419
                        break;
420 295
                }
421
            }
422
        }
423 295
424 295
        $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
425 289
426 295
        return $this->currentPersisterContext->selectColumnListSql;
427
    }
428 295
429 289
    /**
430
     * {@inheritdoc}
431 289
     */
432
    protected function getInsertColumnList()
433
    {
434 295
        // Identifier columns must always come first in the column list of subclasses.
435 295
        $columns       = [];
436 295
        $parentColumns = $this->class->isRootEntity()
437
            ? []
438 293
            : $this->class->getIdentifierColumns($this->em);
439
440
        foreach ($parentColumns as $columnName => $column) {
441 295
            $columns[] = $columnName;
442 265
443 264
            $this->columns[$columnName] = $column;
444
        }
445 264
446
        foreach ($this->class->getPropertiesIterator() as $name => $property) {
447 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...nheritedProperty($name), Probably Intended Meaning: $property instanceof Doc...heritedProperty($name))
Loading history...
448 264
                || ($property instanceof AssociationMetadata && $this->class->isInheritedProperty($name))
449
                /*|| isset($this->class->embeddedClasses[$name])*/) {
450 264
                continue;
451 12
            }
452
453
            if ($property instanceof AssociationMetadata) {
454 264
                if ($property->isOwningSide() && $property instanceof ToOneAssociationMetadata) {
455
                    $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
456 264
457
                    foreach ($property->getJoinColumns() as $joinColumn) {
458
                        /** @var JoinColumnMetadata $joinColumn */
459
                        $columnName           = $joinColumn->getColumnName();
460 265
                        $referencedColumnName = $joinColumn->getReferencedColumnName();
461
462
                        if (! $joinColumn->getType()) {
463 295
                            $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
464 295
                        }
465 291
466 295
                        $columns[] = $columnName;
467
468 233
                        $this->columns[$columnName] = $joinColumn;
469
                    }
470 233
                }
471
472 233
                continue;
473
            }
474
475
            if ($this->class->getClassName() !== $this->class->getRootClassName()
476
                || ! $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

476
                || ! $this->class->getProperty($name)->/** @scrutinizer ignore-call */ hasValueGenerator()
Loading history...
477 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

477
                || $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...
478 295
                || $this->class->identifier[0] !== $name
479 295
            ) {
480
                $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

480
                /** @scrutinizer ignore-call */ 
481
                $columnName = $property->getColumnName();
Loading history...
481 295
482
                $columns[] = $columnName;
483 295
484
                $this->columns[$columnName] = $property;
485
            }
486 295
        }
487
488
        // Add discriminator column if it is the topmost class.
489
        if ($this->class->isRootEntity()) {
490
            $discrColumn     = $this->class->discriminatorColumn;
491
            $discrColumnName = $discrColumn->getColumnName();
492
493
            $columns[] = $discrColumnName;
494 73
495
            $this->columns[$discrColumnName] = $discrColumn;
496 73
        }
497 73
498
        return $columns;
499
    }
500 73
501
    /**
502 73
     * @param string $baseTableAlias
503 49
     *
504 49
     * @return string
505 49
     */
506 49
    private function getJoinSql($baseTableAlias)
507
    {
508 49
        $joinSql           = '';
509 49
        $identifierColumns = $this->class->getIdentifierColumns($this->em);
510
511 49
        // INNER JOIN parent tables
512
        $parentClass = $this->class;
513
514 49
        while (($parentClass = $parentClass->getParent()) !== null) {
515
            if (! $parentClass->isMappedSuperclass) {
516
                $conditions = [];
517
                $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
518 73
                $tableAlias = $this->getSQLTableAlias($parentClass->getTableName());
519 52
                $joinSql   .= ' INNER JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
520 52
521 52
                foreach ($identifierColumns as $idColumn) {
522 52
                    $quotedColumnName = $this->platform->quoteIdentifier($idColumn->getColumnName());
523 52
524
                    $conditions[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
525 52
                }
526 52
527
                $joinSql .= implode(' AND ', $conditions);
528 52
            }
529
        }
530
531 52
        // OUTER JOIN sub tables
532
        foreach ($this->class->getSubClasses() as $subClassName) {
533
            $conditions = [];
534 73
            $subClass   = $this->em->getClassMetadata($subClassName);
535
            $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...
536
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName());
537
            $joinSql   .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
538
539
            foreach ($identifierColumns as $idColumn) {
540
                $quotedColumnName = $this->platform->quoteIdentifier($idColumn->getColumnName());
541
542
                $conditions[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
543
            }
544
545
            $joinSql .= implode(' AND ', $conditions);
546
        }
547
548
        return $joinSql;
549
    }
550
}
551