Failed Conditions
Push — master ( ddb3cd...4476ec )
by Marco
11:47
created

JoinedSubclassPersister   F

Complexity

Total Complexity 82

Size/Duplication

Total Lines 507
Duplicated Lines 0 %

Test Coverage

Coverage 95.63%

Importance

Changes 0
Metric Value
dl 0
loc 507
ccs 241
cts 252
cp 0.9563
rs 1.5789
c 0
b 0
f 0
wmc 82

9 Methods

Rating   Name   Duplication   Size   Complexity  
D getSelectColumnsSQL() 0 87 16
C getInsertColumnList() 0 67 19
F insert() 0 90 15
B getJoinSql() 0 41 5
C getSelectSQL() 0 61 10
B delete() 0 28 3
B getLockTablesSql() 0 25 3
B getCountSQL() 0 26 5
B 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\Types\Type;
10
use Doctrine\ORM\Mapping\AssociationMetadata;
11
use Doctrine\ORM\Mapping\FieldMetadata;
12
use Doctrine\ORM\Mapping\GeneratorType;
13
use Doctrine\ORM\Mapping\JoinColumnMetadata;
14
use Doctrine\ORM\Mapping\ManyToManyAssociationMetadata;
15
use Doctrine\ORM\Mapping\ToManyAssociationMetadata;
16
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
17
use Doctrine\ORM\Mapping\VersionFieldMetadata;
18
use Doctrine\ORM\Utility\PersisterHelper;
19
20
/**
21
 * The joined subclass persister maps a single entity instance to several tables in the
22
 * database as it is defined by the <tt>Class Table Inheritance</tt> strategy.
23
 *
24
 * @see http://martinfowler.com/eaaCatalog/classTableInheritance.html
25
 */
26
class JoinedSubclassPersister extends AbstractEntityInheritancePersister
27
{
28
    /**
29
     * {@inheritdoc}
30
     */
31 288
    public function insert($entity)
32
    {
33 288
        $rootClass      = ! $this->class->isRootEntity()
34 283
            ? $this->em->getClassMetadata($this->class->getRootClassName())
35 288
            : $this->class;
36 288
        $generationPlan = $this->class->getValueGenerationPlan();
37
38
        // Prepare statement for the root table
39 288
        $rootPersister = $this->em->getUnitOfWork()->getEntityPersister($rootClass->getClassName());
40 288
        $rootTableName = $rootClass->getTableName();
41 288
        $rootTableStmt = $this->conn->prepare($rootPersister->getInsertSQL());
42
43
        // Prepare statements for sub tables.
44 288
        $subTableStmts = [];
45
46 288
        if ($rootClass !== $this->class) {
47 283
            $subTableStmts[$this->class->getTableName()] = $this->conn->prepare($this->getInsertSQL());
48
        }
49
50 288
        $parentClass = $this->class;
51
52 288
        while (($parentClass = $parentClass->getParent()) !== null) {
53 283
            $parentTableName = $parentClass->getTableName();
54
55 283
            if ($parentClass !== $rootClass) {
56 166
                $parentPersister = $this->em->getUnitOfWork()->getEntityPersister($parentClass->getClassName());
57
58 166
                $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL());
59
            }
60
        }
61
62
        // Execute all inserts. For each entity:
63
        // 1) Insert on root table
64
        // 2) Insert on sub tables
65 288
        $insertData = $this->prepareInsertData($entity);
66
67
        // Execute insert on root table
68 288
        $paramIndex = 1;
69
70 288
        foreach ($insertData[$rootTableName] as $columnName => $value) {
71 288
            $type = $this->columns[$columnName]->getType();
72
73 288
            $rootTableStmt->bindValue($paramIndex++, $value, $type);
74
        }
75
76 288
        $rootTableStmt->execute();
77
78 288
        if ($generationPlan->containsDeferred()) {
79 285
            $generationPlan->executeDeferred($this->em, $entity);
80 285
            $id = $this->getIdentifier($entity);
81
        } else {
82 3
            $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
83
        }
84
85 288
        if ($this->class->isVersioned()) {
86 8
            $this->assignDefaultVersionValue($entity, $id);
87
        }
88
89
        // Execute inserts on subtables.
90
        // The order doesn't matter because all child tables link to the root table via FK.
91 288
        foreach ($subTableStmts as $tableName => $stmt) {
92
            /** @var \Doctrine\DBAL\Statement $stmt */
93 283
            $paramIndex = 1;
94 283
            $data       = $insertData[$tableName] ?? [];
95
96 283
            foreach ((array) $id as $idName => $idVal) {
97 283
                $type = Type::getType('string');
98
99 283
                if (isset($this->columns[$idName])) {
100 283
                    $type = $this->columns[$idName]->getType();
101
                }
102
103 283
                $stmt->bindValue($paramIndex++, $idVal, $type);
104
            }
105
106 283
            foreach ($data as $columnName => $value) {
107 220
                if (! is_array($id) || ! isset($id[$columnName])) {
108 220
                    $type = $this->columns[$columnName]->getType();
109
110 220
                    $stmt->bindValue($paramIndex++, $value, $type);
111
                }
112
            }
113
114 283
            $stmt->execute();
115
        }
116
117 288
        $rootTableStmt->closeCursor();
118
119 288
        foreach ($subTableStmts as $stmt) {
120 283
            $stmt->closeCursor();
121
        }
122 288
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127 27
    public function update($entity)
128
    {
129 27
        $updateData = $this->prepareUpdateData($entity);
130
131 27
        if (! $updateData) {
132
            return;
133
        }
134
135 27
        $isVersioned = $this->class->isVersioned();
136
137 27
        foreach ($updateData as $tableName => $data) {
138 27
            $versioned = $isVersioned && $this->class->versionProperty->getTableName() === $tableName;
139
140 27
            $this->updateTable($entity, $this->platform->quoteIdentifier($tableName), $data, $versioned);
141
        }
142
143
        // Make sure the table with the version column is updated even if no columns on that
144
        // table were affected.
145 27
        if ($isVersioned) {
146 4
            $versionedClass = $this->class->versionProperty->getDeclaringClass();
147 4
            $versionedTable = $versionedClass->getTableName();
148
149 4
            if (! isset($updateData[$versionedTable])) {
150 1
                $tableName = $versionedClass->table->getQuotedQualifiedName($this->platform);
151
152 1
                $this->updateTable($entity, $tableName, [], true);
153
            }
154
155 4
            $identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
156
157 4
            $this->assignDefaultVersionValue($entity, $identifiers);
158
        }
159 27
    }
160
161
    /**
162
     * {@inheritdoc}
163
     */
164 5
    public function delete($entity)
165
    {
166 5
        $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
167 5
        $id         = array_combine(array_keys($this->class->getIdentifierColumns($this->em)), $identifier);
168
169 5
        $this->deleteJoinTableRecords($identifier);
170
171
        // If the database platform supports FKs, just
172
        // delete the row from the root table. Cascades do the rest.
173 5
        if ($this->platform->supportsForeignKeyConstraints()) {
174
            $rootClass = $this->em->getClassMetadata($this->class->getRootClassName());
175
            $rootTable = $rootClass->table->getQuotedQualifiedName($this->platform);
176
177
            return (bool) $this->conn->delete($rootTable, $id);
178
        }
179
180
        // Delete from all tables individually, starting from this class' table up to the root table.
181 5
        $rootTable    = $this->class->table->getQuotedQualifiedName($this->platform);
182 5
        $affectedRows = $this->conn->delete($rootTable, $id);
183 5
        $parentClass  = $this->class;
184
185 5
        while (($parentClass = $parentClass->getParent()) !== null) {
186 4
            $parentTable = $parentClass->table->getQuotedQualifiedName($this->platform);
187
188 4
            $this->conn->delete($parentTable, $id);
189
        }
190
191 5
        return (bool) $affectedRows;
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197 67
    public function getSelectSQL(
198
        $criteria,
199
        ?AssociationMetadata $association = null,
200
        $lockMode = null,
201
        $limit = null,
202
        $offset = null,
203
        array $orderBy = []
204
    ) {
205 67
        $this->switchPersisterContext($offset, $limit);
206
207 67
        $baseTableAlias = $this->getSQLTableAlias($this->class->getTableName());
208 67
        $joinSql        = $this->getJoinSql($baseTableAlias);
209
210 67
        if ($association instanceof ManyToManyAssociationMetadata) {
211 2
            $joinSql .= $this->getSelectManyToManyJoinSQL($association);
212
        }
213
214 67
        if ($association instanceof ToManyAssociationMetadata && $association->getOrderBy()) {
215 1
            $orderBy = $association->getOrderBy();
216
        }
217
218 67
        $orderBySql   = $this->getOrderBySQL($orderBy, $baseTableAlias);
219 67
        $conditionSql = ($criteria instanceof Criteria)
220
            ? $this->getSelectConditionCriteriaSQL($criteria)
221 67
            : $this->getSelectConditionSQL($criteria, $association);
222
223
        // If the current class in the root entity, add the filters
224 67
        $rootClass  = $this->em->getClassMetadata($this->class->getRootClassName());
225 67
        $tableAlias = $this->getSQLTableAlias($rootClass->getTableName());
226 67
        $filterSql  = $this->generateFilterConditionSQL($rootClass, $tableAlias);
227
228 67
        if ($filterSql) {
229 4
            $conditionSql .= $conditionSql
230 2
                ? ' AND ' . $filterSql
231 4
                : $filterSql;
232
        }
233
234 67
        $lockSql = '';
235
236
        switch ($lockMode) {
237 67
            case LockMode::PESSIMISTIC_READ:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
238
                $lockSql = ' ' . $this->platform->getReadLockSQL();
239
                break;
240
241 67
            case LockMode::PESSIMISTIC_WRITE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
242
                $lockSql = ' ' . $this->platform->getWriteLockSQL();
243
                break;
244
        }
245
246 67
        $tableName  = $this->class->table->getQuotedQualifiedName($this->platform);
247 67
        $from       = ' FROM ' . $tableName . ' ' . $baseTableAlias;
248 67
        $where      = $conditionSql !== '' ? ' WHERE ' . $conditionSql : '';
249 67
        $lock       = $this->platform->appendLockHint($from, $lockMode);
250 67
        $columnList = $this->getSelectColumnsSQL();
251 67
        $query      = 'SELECT ' . $columnList
252 67
                    . $lock
253 67
                    . $joinSql
254 67
                    . $where
255 67
                    . $orderBySql;
256
257 67
        return $this->platform->modifyLimitQuery($query, $limit, $offset) . $lockSql;
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     */
263 6
    public function getCountSQL($criteria = [])
264
    {
265 6
        $tableName      = $this->class->table->getQuotedQualifiedName($this->platform);
266 6
        $baseTableAlias = $this->getSQLTableAlias($this->class->getTableName());
267 6
        $joinSql        = $this->getJoinSql($baseTableAlias);
268
269 6
        $conditionSql = ($criteria instanceof Criteria)
270 1
            ? $this->getSelectConditionCriteriaSQL($criteria)
271 6
            : $this->getSelectConditionSQL($criteria);
272
273 6
        $rootClass  = $this->em->getClassMetadata($this->class->getRootClassName());
274 6
        $tableAlias = $this->getSQLTableAlias($rootClass->getTableName());
275 6
        $filterSql  = $this->generateFilterConditionSQL($rootClass, $tableAlias);
276
277 6
        if ($filterSql !== '') {
278 1
            $conditionSql = $conditionSql
279 1
                ? $conditionSql . ' AND ' . $filterSql
280 1
                : $filterSql;
281
        }
282
283
        $sql = 'SELECT COUNT(*) '
284 6
            . 'FROM ' . $tableName . ' ' . $baseTableAlias
285 6
            . $joinSql
286 6
            . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql);
287
288 6
        return $sql;
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294 6
    protected function getLockTablesSql($lockMode)
295
    {
296 6
        $joinSql           = '';
297 6
        $identifierColumns = $this->class->getIdentifierColumns($this->em);
298 6
        $baseTableAlias    = $this->getSQLTableAlias($this->class->getTableName());
299
300
        // INNER JOIN parent tables
301 6
        $parentClass = $this->class;
302
303 6
        while (($parentClass = $parentClass->getParent()) !== null) {
304 5
            $conditions = [];
305 5
            $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
306 5
            $tableAlias = $this->getSQLTableAlias($parentClass->getTableName());
307 5
            $joinSql   .= ' INNER JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
308
309 5
            foreach ($identifierColumns as $idColumn) {
310 5
                $quotedColumnName = $this->platform->quoteIdentifier($idColumn->getColumnName());
311
312 5
                $conditions[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
313
            }
314
315 5
            $joinSql .= implode(' AND ', $conditions);
316
        }
317
318 6
        return parent::getLockTablesSql($lockMode) . $joinSql;
319
    }
320
321
    /**
322
     * Ensure this method is never called. This persister overrides getSelectEntitiesSQL directly.
323
     *
324
     * @return string
325
     */
326 67
    protected function getSelectColumnsSQL()
327
    {
328
        // Create the column list fragment only once
329 67
        if ($this->currentPersisterContext->selectColumnListSql !== null) {
330 11
            return $this->currentPersisterContext->selectColumnListSql;
331
        }
332
333 67
        $this->currentPersisterContext->rsm->addEntityResult($this->class->getClassName(), 'r');
334
335 67
        $columnList = [];
336
337
        // Add columns
338 67
        foreach ($this->class->getDeclaredPropertiesIterator() as $fieldName => $property) {
339 67
            if ($property instanceof FieldMetadata) {
340 67
                $columnList[] = $this->getSelectColumnSQL($fieldName, $property->getDeclaringClass());
341
342 67
                continue;
343
            }
344
345 55
            if (! ($property instanceof ToOneAssociationMetadata) || ! $property->isOwningSide()) {
346 43
                continue;
347
            }
348
349 54
            $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
350
351 54
            foreach ($property->getJoinColumns() as $joinColumn) {
352
                /** @var JoinColumnMetadata $joinColumn */
353 54
                $referencedColumnName = $joinColumn->getReferencedColumnName();
354
355 54
                if (! $joinColumn->getType()) {
356
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
357
                }
358
359 54
                $columnList[] = $this->getSelectJoinColumnSQL($joinColumn);
360
            }
361
        }
362
363
        // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#processSQLResult).
364 67
        $discrColumn      = $this->class->discriminatorColumn;
365 67
        $discrTableAlias  = $this->getSQLTableAlias($discrColumn->getTableName());
366 67
        $discrColumnName  = $discrColumn->getColumnName();
367 67
        $discrColumnType  = $discrColumn->getType();
368 67
        $resultColumnName = $this->platform->getSQLResultCasing($discrColumnName);
369 67
        $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
370
371 67
        $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName);
372 67
        $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType);
373
374 67
        $columnList[] = $discrColumnType->convertToDatabaseValueSQL($discrTableAlias . '.' . $quotedColumnName, $this->platform);
375
376
        // sub tables
377 67
        foreach ($this->class->getSubClasses() as $subClassName) {
378 48
            $subClass = $this->em->getClassMetadata($subClassName);
379
380
            // Add columns
381 48
            foreach ($subClass->getDeclaredPropertiesIterator() as $fieldName => $property) {
382 48
                if ($subClass->isInheritedProperty($fieldName)) {
383 48
                    continue;
384
                }
385
386
                switch (true) {
387 45
                    case ($property instanceof FieldMetadata):
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
388 45
                        $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass);
389 45
                        break;
390
391 32
                    case ($property instanceof ToOneAssociationMetadata && $property->isOwningSide()):
392 32
                        $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
393
394 32
                        foreach ($property->getJoinColumns() as $joinColumn) {
395
                            /** @var JoinColumnMetadata $joinColumn */
396 32
                            $referencedColumnName = $joinColumn->getReferencedColumnName();
397
398 32
                            if (! $joinColumn->getType()) {
399
                                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
400
                            }
401
402 32
                            $columnList[] = $this->getSelectJoinColumnSQL($joinColumn);
403
                        }
404
405 48
                        break;
406
                }
407
            }
408
        }
409
410 67
        $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
411
412 67
        return $this->currentPersisterContext->selectColumnListSql;
413
    }
414
415
    /**
416
     * {@inheritdoc}
417
     */
418 288
    protected function getInsertColumnList()
419
    {
420
        // Identifier columns must always come first in the column list of subclasses.
421 288
        $columns       = [];
422 288
        $parentColumns = $this->class->getParent()
423 283
            ? $this->class->getIdentifierColumns($this->em)
424 288
            : [];
425
426 288
        foreach ($parentColumns as $columnName => $column) {
427 283
            $columns[] = $columnName;
428
429 283
            $this->columns[$columnName] = $column;
430
        }
431
432 288
        foreach ($this->class->getDeclaredPropertiesIterator() as $name => $property) {
433 288
            if (($property instanceof FieldMetadata && ($property instanceof VersionFieldMetadata || $this->class->isInheritedProperty($name)))
434 288
                || ($property instanceof AssociationMetadata && $this->class->isInheritedProperty($name))
435
                /*|| isset($this->class->embeddedClasses[$name])*/) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
436 286
                continue;
437
            }
438
439 288
            if ($property instanceof AssociationMetadata) {
440 262
                if ($property->isOwningSide() && $property instanceof ToOneAssociationMetadata) {
441 261
                    $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
442
443 261
                    foreach ($property->getJoinColumns() as $joinColumn) {
444
                        /** @var JoinColumnMetadata $joinColumn */
445 261
                        $columnName           = $joinColumn->getColumnName();
446 261
                        $referencedColumnName = $joinColumn->getReferencedColumnName();
447
448 261
                        if (! $joinColumn->getType()) {
449 11
                            $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
450
                        }
451
452 261
                        $columns[] = $columnName;
453
454 261
                        $this->columns[$columnName] = $joinColumn;
455
                    }
456
                }
457
458 262
                continue;
459
            }
460
461 288
            if ($this->class->getClassName() !== $this->class->getRootClassName()
462 288
                || ! $this->class->getProperty($name)->hasValueGenerator()
463 285
                || $this->class->getProperty($name)->getValueGenerator()->getType() !== GeneratorType::IDENTITY
464 288
                || $this->class->identifier[0] !== $name
465
            ) {
466 228
                $columnName = $property->getColumnName();
467
468 228
                $columns[] = $columnName;
469
470 288
                $this->columns[$columnName] = $property;
471
            }
472
        }
473
474
        // Add discriminator column if it is the topmost class.
475 288
        if ($this->class->isRootEntity()) {
476 288
            $discrColumn     = $this->class->discriminatorColumn;
477 288
            $discrColumnName = $discrColumn->getColumnName();
478
479 288
            $columns[] = $discrColumnName;
480
481 288
            $this->columns[$discrColumnName] = $discrColumn;
482
        }
483
484 288
        return $columns;
485
    }
486
487
    /**
488
     * @param string $baseTableAlias
489
     *
490
     * @return string
491
     */
492 72
    private function getJoinSql($baseTableAlias)
493
    {
494 72
        $joinSql           = '';
495 72
        $identifierColumns = $this->class->getIdentifierColumns($this->em);
496
497
        // INNER JOIN parent tables
498 72
        $parentClass = $this->class;
499
500 72
        while (($parentClass = $parentClass->getParent()) !== null) {
501 49
            $conditions = [];
502 49
            $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
503 49
            $tableAlias = $this->getSQLTableAlias($parentClass->getTableName());
504 49
            $joinSql   .= ' INNER JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
505
506 49
            foreach ($identifierColumns as $idColumn) {
507 49
                $quotedColumnName = $this->platform->quoteIdentifier($idColumn->getColumnName());
508
509 49
                $conditions[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
510
            }
511
512 49
            $joinSql .= implode(' AND ', $conditions);
513
        }
514
515
        // OUTER JOIN sub tables
516 72
        foreach ($this->class->getSubClasses() as $subClassName) {
517 50
            $conditions = [];
518 50
            $subClass   = $this->em->getClassMetadata($subClassName);
519 50
            $tableName  = $subClass->table->getQuotedQualifiedName($this->platform);
520 50
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName());
521 50
            $joinSql   .= ' LEFT JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
522
523 50
            foreach ($identifierColumns as $idColumn) {
524 50
                $quotedColumnName = $this->platform->quoteIdentifier($idColumn->getColumnName());
525
526 50
                $conditions[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
527
            }
528
529 50
            $joinSql .= implode(' AND ', $conditions);
530
        }
531
532 72
        return $joinSql;
533
    }
534
}
535