Failed Conditions
Push — master ( 8601d9...376c5e )
by Luís
09:32 queued 11s
created

ORM/Persisters/Entity/JoinedSubclassPersister.php (5 issues)

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());
44 295
        $rootTableName = $rootClass->getTableName();
45 295
        $rootTableStmt = $this->conn->prepare($rootPersister->getInsertSQL());
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
            $parentTableName = $parentClass->getTableName();
58
59 289
            if ($parentClass !== $rootClass) {
60 170
                $parentPersister = $this->em->getUnitOfWork()->getEntityPersister($parentClass->getClassName());
61
62 170
                $subTableStmts[$parentTableName] = $this->conn->prepare($parentPersister->getInsertSQL());
63
            }
64
        }
65
66
        // Execute all inserts. For each entity:
67
        // 1) Insert on root table
68
        // 2) Insert on sub tables
69 295
        $insertData = $this->prepareInsertData($entity);
70
71
        // Execute insert on root table
72 295
        $paramIndex = 1;
73
74 295
        foreach ($insertData[$rootTableName] as $columnName => $value) {
75 295
            $type = $this->columns[$columnName]->getType();
76
77 295
            $rootTableStmt->bindValue($paramIndex++, $value, $type);
78
        }
79
80 295
        $rootTableStmt->execute();
81
82 295
        if ($generationPlan->containsDeferred()) {
83 291
            $generationPlan->executeDeferred($this->em, $entity);
84 291
            $id = $this->getIdentifier($entity);
85
        } else {
86 4
            $id = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
87
        }
88
89 295
        if ($this->class->isVersioned()) {
90 9
            $this->assignDefaultVersionValue($entity, $id);
91
        }
92
93
        // Execute inserts on subtables.
94
        // The order doesn't matter because all child tables link to the root table via FK.
95 295
        foreach ($subTableStmts as $tableName => $stmt) {
96
            /** @var Statement $stmt */
97 289
            $paramIndex = 1;
98 289
            $data       = $insertData[$tableName] ?? [];
99
100 289
            foreach ((array) $id as $idName => $idVal) {
101 289
                $type = Type::getType('string');
102
103 289
                if (isset($this->columns[$idName])) {
104 289
                    $type = $this->columns[$idName]->getType();
105
                }
106
107 289
                $stmt->bindValue($paramIndex++, $idVal, $type);
108
            }
109
110 289
            foreach ($data as $columnName => $value) {
111 224
                if (! is_array($id) || ! isset($id[$columnName])) {
112 224
                    $type = $this->columns[$columnName]->getType();
113
114 224
                    $stmt->bindValue($paramIndex++, $value, $type);
115
                }
116
            }
117
118 289
            $stmt->execute();
119
        }
120
121 295
        $rootTableStmt->closeCursor();
122
123 295
        foreach ($subTableStmts as $stmt) {
124 289
            $stmt->closeCursor();
125
        }
126 295
    }
127
128
    /**
129
     * {@inheritdoc}
130
     */
131 30
    public function update($entity)
132
    {
133 30
        $updateData = $this->prepareUpdateData($entity);
134
135 30
        if (! $updateData) {
136
            return;
137
        }
138
139 30
        $isVersioned = $this->class->isVersioned();
140
141 30
        foreach ($updateData as $tableName => $data) {
142 30
            $versioned = $isVersioned && $this->class->versionProperty->getTableName() === $tableName;
143
144 30
            $this->updateTable($entity, $this->platform->quoteIdentifier($tableName), $data, $versioned);
145
        }
146
147
        // Make sure the table with the version column is updated even if no columns on that
148
        // table were affected.
149 29
        if ($isVersioned) {
150 5
            $versionedClass = $this->class->versionProperty->getDeclaringClass();
151 5
            $versionedTable = $versionedClass->getTableName();
152
153 5
            if (! isset($updateData[$versionedTable])) {
154 2
                $tableName = $versionedClass->table->getQuotedQualifiedName($this->platform);
155
156 2
                $this->updateTable($entity, $tableName, [], true);
157
            }
158
159 4
            $identifiers = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
160
161 4
            $this->assignDefaultVersionValue($entity, $identifiers);
162
        }
163 28
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168 4
    public function delete($entity)
169
    {
170 4
        $identifier = $this->em->getUnitOfWork()->getEntityIdentifier($entity);
171 4
        $id         = array_combine(array_keys($this->class->getIdentifierColumns($this->em)), $identifier);
172
173 4
        $this->deleteJoinTableRecords($identifier);
174
175
        // If the database platform supports FKs, just
176
        // delete the row from the root table. Cascades do the rest.
177 4
        if ($this->platform->supportsForeignKeyConstraints()) {
178
            $rootClass = $this->em->getClassMetadata($this->class->getRootClassName());
179
            $rootTable = $rootClass->table->getQuotedQualifiedName($this->platform);
180
181
            return (bool) $this->conn->delete($rootTable, $id);
182
        }
183
184
        // Delete from all tables individually, starting from this class' table up to the root table.
185 4
        $rootTable    = $this->class->table->getQuotedQualifiedName($this->platform);
186 4
        $affectedRows = $this->conn->delete($rootTable, $id);
187 4
        $parentClass  = $this->class;
188
189 4
        while (($parentClass = $parentClass->getParent()) !== null) {
190 3
            $parentTable = $parentClass->table->getQuotedQualifiedName($this->platform);
191
192 3
            $this->conn->delete($parentTable, $id);
193
        }
194
195 4
        return (bool) $affectedRows;
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201 68
    public function getSelectSQL(
202
        $criteria,
203
        ?AssociationMetadata $association = null,
204
        $lockMode = null,
205
        $limit = null,
206
        $offset = null,
207
        array $orderBy = []
208
    ) {
209 68
        $this->switchPersisterContext($offset, $limit);
210
211 68
        $baseTableAlias = $this->getSQLTableAlias($this->class->getTableName());
212 68
        $joinSql        = $this->getJoinSql($baseTableAlias);
213
214 68
        if ($association instanceof ManyToManyAssociationMetadata) {
215 2
            $joinSql .= $this->getSelectManyToManyJoinSQL($association);
216
        }
217
218 68
        if ($association instanceof ToManyAssociationMetadata && $association->getOrderBy()) {
219 1
            $orderBy = $association->getOrderBy();
220
        }
221
222 68
        $orderBySql   = $this->getOrderBySQL($orderBy, $baseTableAlias);
223 68
        $conditionSql = $criteria instanceof Criteria
224
            ? $this->getSelectConditionCriteriaSQL($criteria)
225 68
            : $this->getSelectConditionSQL($criteria, $association);
226
227
        // If the current class in the root entity, add the filters
228 68
        $rootClass  = $this->em->getClassMetadata($this->class->getRootClassName());
229 68
        $tableAlias = $this->getSQLTableAlias($rootClass->getTableName());
230 68
        $filterSql  = $this->generateFilterConditionSQL($rootClass, $tableAlias);
0 ignored issues
show
$rootClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $targetEntity of Doctrine\ORM\Persisters\...ateFilterConditionSQL(). ( Ignorable by Annotation )

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

230
        $filterSql  = $this->generateFilterConditionSQL(/** @scrutinizer ignore-type */ $rootClass, $tableAlias);
Loading history...
231
232 68
        if ($filterSql) {
233 4
            $conditionSql .= $conditionSql
234 2
                ? ' AND ' . $filterSql
235 4
                : $filterSql;
236
        }
237
238 68
        $lockSql = '';
239
240
        switch ($lockMode) {
241 68
            case LockMode::PESSIMISTIC_READ:
242
                $lockSql = ' ' . $this->platform->getReadLockSQL();
243
                break;
244
245 68
            case LockMode::PESSIMISTIC_WRITE:
246
                $lockSql = ' ' . $this->platform->getWriteLockSQL();
247
                break;
248
        }
249
250 68
        $tableName  = $this->class->table->getQuotedQualifiedName($this->platform);
251 68
        $from       = ' FROM ' . $tableName . ' ' . $baseTableAlias;
252 68
        $where      = $conditionSql !== '' ? ' WHERE ' . $conditionSql : '';
253 68
        $lock       = $this->platform->appendLockHint($from, $lockMode);
254 68
        $columnList = $this->getSelectColumnsSQL();
255 68
        $query      = 'SELECT ' . $columnList
256 68
                    . $lock
257 68
                    . $joinSql
258 68
                    . $where
259 68
                    . $orderBySql;
260
261 68
        return $this->platform->modifyLimitQuery($query, $limit, $offset ?? 0) . $lockSql;
262
    }
263
264
    /**
265
     * {@inheritDoc}
266
     */
267 6
    public function getCountSQL($criteria = [])
268
    {
269 6
        $tableName      = $this->class->table->getQuotedQualifiedName($this->platform);
270 6
        $baseTableAlias = $this->getSQLTableAlias($this->class->getTableName());
271 6
        $joinSql        = $this->getJoinSql($baseTableAlias);
272
273 6
        $conditionSql = $criteria instanceof Criteria
274 1
            ? $this->getSelectConditionCriteriaSQL($criteria)
275 6
            : $this->getSelectConditionSQL($criteria);
276
277 6
        $rootClass  = $this->em->getClassMetadata($this->class->getRootClassName());
278 6
        $tableAlias = $this->getSQLTableAlias($rootClass->getTableName());
279 6
        $filterSql  = $this->generateFilterConditionSQL($rootClass, $tableAlias);
0 ignored issues
show
$rootClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $targetEntity of Doctrine\ORM\Persisters\...ateFilterConditionSQL(). ( Ignorable by Annotation )

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

279
        $filterSql  = $this->generateFilterConditionSQL(/** @scrutinizer ignore-type */ $rootClass, $tableAlias);
Loading history...
280
281 6
        if ($filterSql !== '') {
282 1
            $conditionSql = $conditionSql
283 1
                ? $conditionSql . ' AND ' . $filterSql
284 1
                : $filterSql;
285
        }
286
287
        return 'SELECT COUNT(*) '
288 6
            . 'FROM ' . $tableName . ' ' . $baseTableAlias
289 6
            . $joinSql
290 6
            . (empty($conditionSql) ? '' : ' WHERE ' . $conditionSql);
291
    }
292
293
    /**
294
     * {@inheritdoc}
295
     */
296 6
    protected function getLockTablesSql($lockMode)
297
    {
298 6
        $joinSql           = '';
299 6
        $identifierColumns = $this->class->getIdentifierColumns($this->em);
300 6
        $baseTableAlias    = $this->getSQLTableAlias($this->class->getTableName());
301
302
        // INNER JOIN parent tables
303 6
        $parentClass = $this->class;
304
305 6
        while (($parentClass = $parentClass->getParent()) !== null) {
306 5
            $conditions = [];
307 5
            $tableName  = $parentClass->table->getQuotedQualifiedName($this->platform);
308 5
            $tableAlias = $this->getSQLTableAlias($parentClass->getTableName());
309 5
            $joinSql   .= ' INNER JOIN ' . $tableName . ' ' . $tableAlias . ' ON ';
310
311 5
            foreach ($identifierColumns as $idColumn) {
312 5
                $quotedColumnName = $this->platform->quoteIdentifier($idColumn->getColumnName());
313
314 5
                $conditions[] = $baseTableAlias . '.' . $quotedColumnName . ' = ' . $tableAlias . '.' . $quotedColumnName;
315
            }
316
317 5
            $joinSql .= implode(' AND ', $conditions);
318
        }
319
320 6
        return parent::getLockTablesSql($lockMode) . $joinSql;
321
    }
322
323
    /**
324
     * Ensure this method is never called. This persister overrides getSelectEntitiesSQL directly.
325
     *
326
     * @return string
327
     */
328 68
    protected function getSelectColumnsSQL()
329
    {
330
        // Create the column list fragment only once
331 68
        if ($this->currentPersisterContext->selectColumnListSql !== null) {
332 12
            return $this->currentPersisterContext->selectColumnListSql;
333
        }
334
335 68
        $this->currentPersisterContext->rsm->addEntityResult($this->class->getClassName(), 'r');
336
337 68
        $columnList = [];
338
339
        // Add columns
340 68
        foreach ($this->class->getPropertiesIterator() as $fieldName => $property) {
341 68
            if ($property instanceof FieldMetadata) {
342 68
                $columnList[] = $this->getSelectColumnSQL($fieldName, $property->getDeclaringClass());
343
344 68
                continue;
345
            }
346
347 56
            if (! ($property instanceof ToOneAssociationMetadata) || ! $property->isOwningSide()) {
348 44
                continue;
349
            }
350
351 55
            $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
352
353 55
            foreach ($property->getJoinColumns() as $joinColumn) {
354
                /** @var JoinColumnMetadata $joinColumn */
355 55
                $referencedColumnName = $joinColumn->getReferencedColumnName();
356
357 55
                if (! $joinColumn->getType()) {
358
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
0 ignored issues
show
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Utility\Per...lper::getTypeOfColumn(). ( Ignorable by Annotation )

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

358
                    $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, /** @scrutinizer ignore-type */ $targetClass, $this->em));
Loading history...
359
                }
360
361 55
                $columnList[] = $this->getSelectJoinColumnSQL($joinColumn);
362
            }
363
        }
364
365
        // Add discriminator column (DO NOT ALIAS, see AbstractEntityInheritancePersister#processSQLResult).
366 68
        $discrColumn      = $this->class->discriminatorColumn;
367 68
        $discrTableAlias  = $this->getSQLTableAlias($discrColumn->getTableName());
368 68
        $discrColumnName  = $discrColumn->getColumnName();
369 68
        $discrColumnType  = $discrColumn->getType();
370 68
        $resultColumnName = $this->platform->getSQLResultCasing($discrColumnName);
371 68
        $quotedColumnName = $this->platform->quoteIdentifier($discrColumn->getColumnName());
372
373 68
        $this->currentPersisterContext->rsm->setDiscriminatorColumn('r', $resultColumnName);
374 68
        $this->currentPersisterContext->rsm->addMetaResult('r', $resultColumnName, $discrColumnName, false, $discrColumnType);
375
376 68
        $columnList[] = $discrColumnType->convertToDatabaseValueSQL($discrTableAlias . '.' . $quotedColumnName, $this->platform);
377
378
        // sub tables
379 68
        foreach ($this->class->getSubClasses() as $subClassName) {
380 50
            $subClass = $this->em->getClassMetadata($subClassName);
381
382
            // Add columns
383 50
            foreach ($subClass->getPropertiesIterator() as $fieldName => $property) {
384 50
                if ($subClass->isInheritedProperty($fieldName)) {
385 50
                    continue;
386
                }
387
388
                switch (true) {
389 46
                    case $property instanceof FieldMetadata:
390 46
                        $columnList[] = $this->getSelectColumnSQL($fieldName, $subClass);
0 ignored issues
show
$subClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Persisters\...r::getSelectColumnSQL(). ( Ignorable by Annotation )

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

390
                        $columnList[] = $this->getSelectColumnSQL($fieldName, /** @scrutinizer ignore-type */ $subClass);
Loading history...
391 46
                        break;
392
393 32
                    case $property instanceof ToOneAssociationMetadata && $property->isOwningSide():
394 32
                        $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
395
396 32
                        foreach ($property->getJoinColumns() as $joinColumn) {
397
                            /** @var JoinColumnMetadata $joinColumn */
398 32
                            $referencedColumnName = $joinColumn->getReferencedColumnName();
399
400 32
                            if (! $joinColumn->getType()) {
401
                                $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
402
                            }
403
404 32
                            $columnList[] = $this->getSelectJoinColumnSQL($joinColumn);
405
                        }
406
407 32
                        break;
408
                }
409
            }
410
        }
411
412 68
        $this->currentPersisterContext->selectColumnListSql = implode(', ', $columnList);
413
414 68
        return $this->currentPersisterContext->selectColumnListSql;
415
    }
416
417
    /**
418
     * {@inheritdoc}
419
     */
420 295
    protected function getInsertColumnList()
421
    {
422
        // Identifier columns must always come first in the column list of subclasses.
423 295
        $columns       = [];
424 295
        $parentColumns = $this->class->getParent()
425 289
            ? $this->class->getIdentifierColumns($this->em)
426 295
            : [];
427
428 295
        foreach ($parentColumns as $columnName => $column) {
429 289
            $columns[] = $columnName;
430
431 289
            $this->columns[$columnName] = $column;
432
        }
433
434 295
        foreach ($this->class->getPropertiesIterator() as $name => $property) {
435 295
            if (($property instanceof FieldMetadata && ($property->isVersioned() || $this->class->isInheritedProperty($name)))
436 295
                || ($property instanceof AssociationMetadata && $this->class->isInheritedProperty($name))
437
                /*|| isset($this->class->embeddedClasses[$name])*/) {
438 293
                continue;
439
            }
440
441 295
            if ($property instanceof AssociationMetadata) {
442 265
                if ($property->isOwningSide() && $property instanceof ToOneAssociationMetadata) {
443 264
                    $targetClass = $this->em->getClassMetadata($property->getTargetEntity());
444
445 264
                    foreach ($property->getJoinColumns() as $joinColumn) {
446
                        /** @var JoinColumnMetadata $joinColumn */
447 264
                        $columnName           = $joinColumn->getColumnName();
448 264
                        $referencedColumnName = $joinColumn->getReferencedColumnName();
449
450 264
                        if (! $joinColumn->getType()) {
451 12
                            $joinColumn->setType(PersisterHelper::getTypeOfColumn($referencedColumnName, $targetClass, $this->em));
0 ignored issues
show
$targetClass of type Doctrine\Common\Persistence\Mapping\ClassMetadata is incompatible with the type Doctrine\ORM\Mapping\ClassMetadata expected by parameter $class of Doctrine\ORM\Utility\Per...lper::getTypeOfColumn(). ( Ignorable by Annotation )

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

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