Completed
Push — master ( 633c84...6d428c )
by Marco
131:48 queued 119:58
created

SqlWalker::generateRangeVariableDeclarationSQL()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 14
cts 14
cp 1
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 15
nc 6
nop 2
crap 4
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Query;
21
22
use Doctrine\DBAL\LockMode;
23
use Doctrine\DBAL\Types\Type;
24
use Doctrine\ORM\Mapping\ClassMetadata;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
use Doctrine\ORM\OptimisticLockException;
27
use Doctrine\ORM\Query;
28
use Doctrine\ORM\Utility\PersisterHelper;
29
30
/**
31
 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
32
 * the corresponding SQL.
33
 *
34
 * @author Guilherme Blanco <[email protected]>
35
 * @author Roman Borschel <[email protected]>
36
 * @author Benjamin Eberlei <[email protected]>
37
 * @author Alexander <[email protected]>
38
 * @author Fabio B. Silva <[email protected]>
39
 * @since  2.0
40
 * @todo Rename: SQLWalker
41
 */
42
class SqlWalker implements TreeWalker
43
{
44
    /**
45
     * @var string
46
     */
47
    const HINT_DISTINCT = 'doctrine.distinct';
48
49
    /**
50
     * @var ResultSetMapping
51
     */
52
    private $rsm;
53
54
    /**
55
     * Counter for generating unique column aliases.
56
     *
57
     * @var integer
58
     */
59
    private $aliasCounter = 0;
60
61
    /**
62
     * Counter for generating unique table aliases.
63
     *
64
     * @var integer
65
     */
66
    private $tableAliasCounter = 0;
67
68
    /**
69
     * Counter for generating unique scalar result.
70
     *
71
     * @var integer
72
     */
73
    private $scalarResultCounter = 1;
74
75
    /**
76
     * Counter for generating unique parameter indexes.
77
     *
78
     * @var integer
79
     */
80
    private $sqlParamIndex = 0;
81
82
    /**
83
     * Counter for generating indexes.
84
     *
85
     * @var integer
86
     */
87
    private $newObjectCounter = 0;
88
89
    /**
90
     * @var ParserResult
91
     */
92
    private $parserResult;
93
94
    /**
95
     * @var \Doctrine\ORM\EntityManager
96
     */
97
    private $em;
98
99
    /**
100
     * @var \Doctrine\DBAL\Connection
101
     */
102
    private $conn;
103
104
    /**
105
     * @var \Doctrine\ORM\AbstractQuery
106
     */
107
    private $query;
108
109
    /**
110
     * @var array
111
     */
112
    private $tableAliasMap = [];
113
114
    /**
115
     * Map from result variable names to their SQL column alias names.
116
     *
117
     * @var array
118
     */
119
    private $scalarResultAliasMap = [];
120
121
    /**
122
     * Map from Table-Alias + Column-Name to OrderBy-Direction.
123
     *
124
     * @var array
125
     */
126
    private $orderedColumnsMap = [];
127
128
    /**
129
     * Map from DQL-Alias + Field-Name to SQL Column Alias.
130
     *
131
     * @var array
132
     */
133
    private $scalarFields = [];
134
135
    /**
136
     * Map of all components/classes that appear in the DQL query.
137
     *
138
     * @var array
139
     */
140
    private $queryComponents;
141
142
    /**
143
     * A list of classes that appear in non-scalar SelectExpressions.
144
     *
145
     * @var array
146
     */
147
    private $selectedClasses = [];
148
149
    /**
150
     * The DQL alias of the root class of the currently traversed query.
151
     *
152
     * @var array
153
     */
154
    private $rootAliases = [];
155
156
    /**
157
     * Flag that indicates whether to generate SQL table aliases in the SQL.
158
     * These should only be generated for SELECT queries, not for UPDATE/DELETE.
159
     *
160
     * @var boolean
161
     */
162
    private $useSqlTableAliases = true;
163
164
    /**
165
     * The database platform abstraction.
166
     *
167
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
168
     */
169
    private $platform;
170
171
    /**
172
     * The quote strategy.
173
     *
174
     * @var \Doctrine\ORM\Mapping\QuoteStrategy
175
     */
176
    private $quoteStrategy;
177
178
    /**
179
     * {@inheritDoc}
180
     */
181 693
    public function __construct($query, $parserResult, array $queryComponents)
182
    {
183 693
        $this->query            = $query;
184 693
        $this->parserResult     = $parserResult;
185 693
        $this->queryComponents  = $queryComponents;
186 693
        $this->rsm              = $parserResult->getResultSetMapping();
187 693
        $this->em               = $query->getEntityManager();
188 693
        $this->conn             = $this->em->getConnection();
189 693
        $this->platform         = $this->conn->getDatabasePlatform();
190 693
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
191 693
    }
192
193
    /**
194
     * Gets the Query instance used by the walker.
195
     *
196
     * @return Query.
0 ignored issues
show
Documentation introduced by
The doc-type Query. could not be parsed: Unknown type name "Query." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
197
     */
198
    public function getQuery()
199
    {
200
        return $this->query;
201
    }
202
203
    /**
204
     * Gets the Connection used by the walker.
205
     *
206
     * @return \Doctrine\DBAL\Connection
207
     */
208 35
    public function getConnection()
209
    {
210 35
        return $this->conn;
211
    }
212
213
    /**
214
     * Gets the EntityManager used by the walker.
215
     *
216
     * @return \Doctrine\ORM\EntityManager
217
     */
218 22
    public function getEntityManager()
219
    {
220 22
        return $this->em;
221
    }
222
223
    /**
224
     * Gets the information about a single query component.
225
     *
226
     * @param string $dqlAlias The DQL alias.
227
     *
228
     * @return array
229
     */
230 17
    public function getQueryComponent($dqlAlias)
231
    {
232 17
        return $this->queryComponents[$dqlAlias];
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function getQueryComponents()
239
    {
240
        return $this->queryComponents;
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246 1 View Code Duplication
    public function setQueryComponent($dqlAlias, array $queryComponent)
247
    {
248 1
        $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
249
250 1
        if (array_diff($requiredKeys, array_keys($queryComponent))) {
251 1
            throw QueryException::invalidQueryComponent($dqlAlias);
252
        }
253
254
        $this->queryComponents[$dqlAlias] = $queryComponent;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 687
    public function getExecutor($AST)
261
    {
262
        switch (true) {
263 687
            case ($AST instanceof AST\DeleteStatement):
264 38
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
265
266 38
                return ($primaryClass->isInheritanceTypeJoined())
267 2
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
268 38
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
269
270 653
            case ($AST instanceof AST\UpdateStatement):
271 29
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
272
273 29
                return ($primaryClass->isInheritanceTypeJoined())
274 4
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
275 29
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
276
277
            default:
278 628
                return new Exec\SingleSelectExecutor($AST, $this);
279
        }
280
    }
281
282
    /**
283
     * Generates a unique, short SQL table alias.
284
     *
285
     * @param string $tableName Table name
286
     * @param string $dqlAlias  The DQL alias.
287
     *
288
     * @return string Generated table alias.
289
     */
290 639
    public function getSQLTableAlias($tableName, $dqlAlias = '')
291
    {
292 639
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
293
294 639
        if ( ! isset($this->tableAliasMap[$tableName])) {
295 639
            $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
296 639
                . $this->tableAliasCounter++ . '_';
297
        }
298
299 639
        return $this->tableAliasMap[$tableName];
300
    }
301
302
    /**
303
     * Forces the SqlWalker to use a specific alias for a table name, rather than
304
     * generating an alias on its own.
305
     *
306
     * @param string $tableName
307
     * @param string $alias
308
     * @param string $dqlAlias
309
     *
310
     * @return string
311
     */
312 65
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
313
    {
314 65
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
315
316 65
        $this->tableAliasMap[$tableName] = $alias;
317
318 65
        return $alias;
319
    }
320
321
    /**
322
     * Gets an SQL column alias for a column name.
323
     *
324
     * @param string $columnName
325
     *
326
     * @return string
327
     */
328 628
    public function getSQLColumnAlias($columnName)
329
    {
330 628
        return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform);
331
    }
332
333
    /**
334
     * Generates the SQL JOINs that are necessary for Class Table Inheritance
335
     * for the given class.
336
     *
337
     * @param ClassMetadata $class    The class for which to generate the joins.
338
     * @param string        $dqlAlias The DQL alias of the class.
339
     *
340
     * @return string The SQL.
341
     */
342 94
    private function _generateClassTableInheritanceJoins($class, $dqlAlias)
343
    {
344 94
        $sql = '';
345
346 94
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
347
348
        // INNER JOIN parent class tables
349 94
        foreach ($class->parentClasses as $parentClassName) {
350 67
            $parentClass = $this->em->getClassMetadata($parentClassName);
351 67
            $tableAlias  = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
352
353
            // If this is a joined association we must use left joins to preserve the correct result.
354 67
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
355 67
            $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
356
357 67
            $sqlParts = [];
358
359 67 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
360 67
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
361
            }
362
363
            // Add filters on the root class
364 67
            if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
365 1
                $sqlParts[] = $filterSql;
366
            }
367
368 67
            $sql .= implode(' AND ', $sqlParts);
369
        }
370
371
        // Ignore subclassing inclusion if partial objects is disallowed
372 94
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
373 21
            return $sql;
374
        }
375
376
        // LEFT JOIN child class tables
377 73
        foreach ($class->subClasses as $subClassName) {
378 33
            $subClass   = $this->em->getClassMetadata($subClassName);
379 33
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
380
381 33
            $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
382
383 33
            $sqlParts = [];
384
385 33 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
386 33
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
387
            }
388
389 33
            $sql .= implode(' AND ', $sqlParts);
390
        }
391
392 73
        return $sql;
393
    }
394
395
    /**
396
     * @return string
397
     */
398 622
    private function _generateOrderedCollectionOrderByItems()
399
    {
400 622
        $orderedColumns = [];
401
402 622
        foreach ($this->selectedClasses as $selectedClass) {
403 484
            $dqlAlias  = $selectedClass['dqlAlias'];
404 484
            $qComp     = $this->queryComponents[$dqlAlias];
405
406 484
            if ( ! isset($qComp['relation']['orderBy'])) {
407 484
                continue;
408
            }
409
410 6
            $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
411
412 6
            foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
413 6
                $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
414 6
                $tableName  = ($qComp['metadata']->isInheritanceTypeJoined())
415 1
                    ? $persister->getOwningTable($fieldName)
416 6
                    : $qComp['metadata']->getTableName();
417
418 6
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
419
420
                // OrderByClause should replace an ordered relation. see - DDC-2475
421 6
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
422 1
                    continue;
423
                }
424
425 6
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
426 6
                $orderedColumns[] = $orderedColumn . ' ' . $orientation;
427
            }
428
        }
429
430 622
        return implode(', ', $orderedColumns);
431
    }
432
433
    /**
434
     * Generates a discriminator column SQL condition for the class with the given DQL alias.
435
     *
436
     * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
437
     *
438
     * @return string
439
     */
440 682
    private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
441
    {
442 682
        $sqlParts = [];
443
444 682
        foreach ($dqlAliases as $dqlAlias) {
445 682
            $class = $this->queryComponents[$dqlAlias]['metadata'];
446
447 682
            if ( ! $class->isInheritanceTypeSingleTable()) continue;
448
449 40
            $conn   = $this->em->getConnection();
450 40
            $values = [];
451
452 40
            if ($class->discriminatorValue !== null) { // discriminators can be 0
453 21
                $values[] = $conn->quote($class->discriminatorValue);
454
            }
455
456 40
            foreach ($class->subClasses as $subclassName) {
457 29
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
458
            }
459
460 40
            $sqlTableAlias = ($this->useSqlTableAliases)
461 35
                ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
462 40
                : '';
463
464 40
            $sqlParts[] = $sqlTableAlias . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
465
        }
466
467 682
        $sql = implode(' AND ', $sqlParts);
468
469 682
        return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
470
    }
471
472
    /**
473
     * Generates the filter SQL for a given entity and table alias.
474
     *
475
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
476
     * @param string        $targetTableAlias The table alias of the joined/selected table.
477
     *
478
     * @return string The SQL query part to add to a query.
479
     */
480 322
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
481
    {
482 322
        if (!$this->em->hasFilters()) {
483 284
            return '';
484
        }
485
486 43
        switch($targetEntity->inheritanceType) {
487 43
            case ClassMetadata::INHERITANCE_TYPE_NONE:
488 33
                break;
489 10
            case ClassMetadata::INHERITANCE_TYPE_JOINED:
490
                // The classes in the inheritance will be added to the query one by one,
491
                // but only the root node is getting filtered
492 6
                if ($targetEntity->name !== $targetEntity->rootEntityName) {
493 4
                    return '';
494
                }
495 6
                break;
496 4
            case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
497
                // With STI the table will only be queried once, make sure that the filters
498
                // are added to the root entity
499 4
                $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
500 4
                break;
501
            default:
502
                //@todo: throw exception?
503
                return '';
504
        }
505
506 43
        $filterClauses = [];
507 43
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
508 10
            if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
509 10
                $filterClauses[] = '(' . $filterExpr . ')';
510
            }
511
        }
512
513 43
        return implode(' AND ', $filterClauses);
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     */
519 628
    public function walkSelectStatement(AST\SelectStatement $AST)
520
    {
521 628
        $limit    = $this->query->getMaxResults();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\ORM\AbstractQuery as the method getMaxResults() does only exist in the following sub-classes of Doctrine\ORM\AbstractQuery: Doctrine\ORM\Query. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
522 628
        $offset   = $this->query->getFirstResult();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\ORM\AbstractQuery as the method getFirstResult() does only exist in the following sub-classes of Doctrine\ORM\AbstractQuery: Doctrine\ORM\Query. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
523 628
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
524 628
        $sql      = $this->walkSelectClause($AST->selectClause)
525 628
            . $this->walkFromClause($AST->fromClause)
526 626
            . $this->walkWhereClause($AST->whereClause);
0 ignored issues
show
Bug introduced by
It seems like $AST->whereClause can be null; however, walkWhereClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
527
528 623
        if ($AST->groupByClause) {
529 24
            $sql .= $this->walkGroupByClause($AST->groupByClause);
530
        }
531
532 623
        if ($AST->havingClause) {
533 14
            $sql .= $this->walkHavingClause($AST->havingClause);
534
        }
535
536 623
        if ($AST->orderByClause) {
537 142
            $sql .= $this->walkOrderByClause($AST->orderByClause);
538
        }
539
540 622
        if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) {
541 6
            $sql .= ' ORDER BY ' . $orderBySql;
542
        }
543
544 622 View Code Duplication
        if ($limit !== null || $offset !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
545 39
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
546
        }
547
548 622
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
549 617
            return $sql;
550
        }
551
552 5
        if ($lockMode === LockMode::PESSIMISTIC_READ) {
553 3
            return $sql . ' ' . $this->platform->getReadLockSQL();
554
        }
555
556 2
        if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
557 1
            return $sql . ' ' . $this->platform->getWriteLockSQL();
558
        }
559
560 1
        if ($lockMode !== LockMode::OPTIMISTIC) {
561
            throw QueryException::invalidLockMode();
562
        }
563
564 1
        foreach ($this->selectedClasses as $selectedClass) {
565 1
            if ( ! $selectedClass['class']->isVersioned) {
566 1
                throw OptimisticLockException::lockFailed($selectedClass['class']->name);
567
            }
568
        }
569
570
        return $sql;
571
    }
572
573
    /**
574
     * {@inheritdoc}
575
     */
576 25
    public function walkUpdateStatement(AST\UpdateStatement $AST)
577
    {
578 25
        $this->useSqlTableAliases = false;
579 25
        $this->rsm->isSelect      = false;
580
581 25
        return $this->walkUpdateClause($AST->updateClause)
582 25
            . $this->walkWhereClause($AST->whereClause);
0 ignored issues
show
Bug introduced by
It seems like $AST->whereClause can be null; however, walkWhereClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
583
    }
584
585
    /**
586
     * {@inheritdoc}
587
     */
588 36
    public function walkDeleteStatement(AST\DeleteStatement $AST)
589
    {
590 36
        $this->useSqlTableAliases = false;
591 36
        $this->rsm->isSelect      = false;
592
593 36
        return $this->walkDeleteClause($AST->deleteClause)
594 36
            . $this->walkWhereClause($AST->whereClause);
0 ignored issues
show
Bug introduced by
It seems like $AST->whereClause can be null; however, walkWhereClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
595
    }
596
597
    /**
598
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
599
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
600
     *
601
     * @param string $identVariable
602
     *
603
     * @return string
604
     */
605 2
    public function walkEntityIdentificationVariable($identVariable)
606
    {
607 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
608 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
609 2
        $sqlParts   = [];
610
611 2
        foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
612 2
            $sqlParts[] = $tableAlias . '.' . $columnName;
613
        }
614
615 2
        return implode(', ', $sqlParts);
616
    }
617
618
    /**
619
     * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
620
     *
621
     * @param string $identificationVariable
622
     * @param string $fieldName
623
     *
624
     * @return string The SQL.
625
     */
626 426
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
627
    {
628 426
        $class = $this->queryComponents[$identificationVariable]['metadata'];
629
630
        if (
631 426
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
632 426
            isset($class->fieldMappings[$fieldName]['inherited'])
633
        ) {
634 38
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
635
        }
636
637 426
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
638
    }
639
640
    /**
641
     * {@inheritdoc}
642
     */
643 495
    public function walkPathExpression($pathExpr)
644
    {
645 495
        $sql = '';
646
647
        /* @var $pathExpr Query\AST\PathExpression */
648 495
        switch ($pathExpr->type) {
649 495
            case AST\PathExpression::TYPE_STATE_FIELD:
650 474
                $fieldName = $pathExpr->field;
651 474
                $dqlAlias = $pathExpr->identificationVariable;
652 474
                $class = $this->queryComponents[$dqlAlias]['metadata'];
653
654 474
                if ($this->useSqlTableAliases) {
655 426
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
656
                }
657
658 474
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
659 474
                break;
660
661 62
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
662
                // 1- the owning side:
663
                //    Just use the foreign key, i.e. u.group_id
664 62
                $fieldName = $pathExpr->field;
665 62
                $dqlAlias = $pathExpr->identificationVariable;
666 62
                $class = $this->queryComponents[$dqlAlias]['metadata'];
667
668 62
                if (isset($class->associationMappings[$fieldName]['inherited'])) {
669 2
                    $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
670
                }
671
672 62
                $assoc = $class->associationMappings[$fieldName];
673
674 62
                if ( ! $assoc['isOwningSide']) {
675 2
                    throw QueryException::associationPathInverseSideNotSupported($pathExpr);
676
                }
677
678
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
679 60
                if (count($assoc['sourceToTargetKeyColumns']) > 1) {
680 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
681
                }
682
683 59
                if ($this->useSqlTableAliases) {
684 56
                    $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
685
                }
686
687 59
                $sql .= reset($assoc['targetToSourceKeyColumns']);
688 59
                break;
689
690
            default:
691
                throw QueryException::invalidPathExpression($pathExpr);
692
        }
693
694 492
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 628
    public function walkSelectClause($selectClause)
701
    {
702 628
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
703 628
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
704
705 628
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
706 1
            $this->query->setHint(self::HINT_DISTINCT, true);
707
        }
708
709 628
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
710 465
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
711
            ||
712 290
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
713 628
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
714
715 628
        foreach ($this->selectedClasses as $selectedClass) {
716 490
            $class       = $selectedClass['class'];
717 490
            $dqlAlias    = $selectedClass['dqlAlias'];
718 490
            $resultAlias = $selectedClass['resultAlias'];
719
720
            // Register as entity or joined entity result
721 490
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
722 490
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
723
            } else {
724 159
                $this->rsm->addJoinedEntityResult(
725 159
                    $class->name,
726 159
                    $dqlAlias,
727 159
                    $this->queryComponents[$dqlAlias]['parent'],
728 159
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
729
                );
730
            }
731
732 490
            if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
733
                // Add discriminator columns to SQL
734 91
                $rootClass   = $this->em->getClassMetadata($class->rootEntityName);
735 91
                $tblAlias    = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
736 91
                $discrColumn = $rootClass->discriminatorColumn;
737 91
                $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
738
739 91
                $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
740
741 91
                $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
742 91
                $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], false, $discrColumn['type']);
743
            }
744
745
            // Add foreign key columns to SQL, if necessary
746 490
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
747 181
                continue;
748
            }
749
750
            // Add foreign key columns of class and also parent classes
751 359
            foreach ($class->associationMappings as $assoc) {
752 319
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
753 268
                    continue;
754 286
                } else if ( !$addMetaColumns && !isset($assoc['id'])) {
755
                    continue;
756
                }
757
758 286
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
759 286
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
760 286
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
761 286
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
762
763 286 View Code Duplication
                foreach ($assoc['joinColumns'] as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
764 286
                    $columnName  = $joinColumn['name'];
765 286
                    $columnAlias = $this->getSQLColumnAlias($columnName);
766 286
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
767
768 286
                    $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
769 286
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
770
771 286
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
772
                }
773
            }
774
775
            // Add foreign key columns to SQL, if necessary
776 359
            if ( ! $addMetaColumns) {
777 8
                continue;
778
            }
779
780
            // Add foreign key columns of subclasses
781 354
            foreach ($class->subClasses as $subClassName) {
782 31
                $subClass      = $this->em->getClassMetadata($subClassName);
783 31
                $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
784
785 31
                foreach ($subClass->associationMappings as $assoc) {
786
                    // Skip if association is inherited
787 27
                    if (isset($assoc['inherited'])) continue;
788
789 16
                    if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
790 14
                        $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
791
792 14 View Code Duplication
                        foreach ($assoc['joinColumns'] as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
793 14
                            $columnName  = $joinColumn['name'];
794 14
                            $columnAlias = $this->getSQLColumnAlias($columnName);
795 14
                            $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
796
797 14
                            $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $subClass, $this->platform);
798 14
                            $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
799
800 354
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
801
                        }
802
                    }
803
                }
804
            }
805
        }
806
807 628
        $sql .= implode(', ', $sqlSelectExpressions);
808
809 628
        return $sql;
810
    }
811
812
    /**
813
     * {@inheritdoc}
814
     */
815 630 View Code Duplication
    public function walkFromClause($fromClause)
816
    {
817 630
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
818 630
        $sqlParts = [];
819
820 630
        foreach ($identificationVarDecls as $identificationVariableDecl) {
821 630
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
822
        }
823
824 628
        return ' FROM ' . implode(', ', $sqlParts);
825
    }
826
827
    /**
828
     * Walks down a IdentificationVariableDeclaration AST node, thereby generating the appropriate SQL.
829
     *
830
     * @param AST\IdentificationVariableDeclaration $identificationVariableDecl
831
     *
832
     * @return string
833
     */
834 631
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
835
    {
836 631
        $sql = $this->walkRangeVariableDeclaration($identificationVariableDecl->rangeVariableDeclaration);
0 ignored issues
show
Bug introduced by
It seems like $identificationVariableD...angeVariableDeclaration can be null; however, walkRangeVariableDeclaration() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
837
838 631
        if ($identificationVariableDecl->indexBy) {
839 6
            $this->walkIndexBy($identificationVariableDecl->indexBy);
840
        }
841
842 631
        foreach ($identificationVariableDecl->joins as $join) {
843 247
            $sql .= $this->walkJoin($join);
844
        }
845
846 629
        return $sql;
847
    }
848
849
    /**
850
     * Walks down a IndexBy AST node.
851
     *
852
     * @param AST\IndexBy $indexBy
853
     *
854
     * @return void
855
     */
856 9
    public function walkIndexBy($indexBy)
857
    {
858 9
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
859 9
        $alias          = $pathExpression->identificationVariable;
860 9
        $field          = $pathExpression->field;
861
862 9
        if (isset($this->scalarFields[$alias][$field])) {
863
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
864
865
            return;
866
        }
867
868 9
        $this->rsm->addIndexBy($alias, $field);
869 9
    }
870
871
    /**
872
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
873
     *
874
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
875
     *
876
     * @return string
877
     */
878 631
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
879
    {
880 631
        return $this->generateRangeVariableDeclarationSQL($rangeVariableDeclaration, false);
881
    }
882
883
    /**
884
     * Generate appropriate SQL for RangeVariableDeclaration AST node
885
     *
886
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
887
     * @param bool $buildNestedJoins
888
     *
889
     * @return string
890
     */
891 631
    private function generateRangeVariableDeclarationSQL($rangeVariableDeclaration, bool $buildNestedJoins) : string
892
    {
893 631
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
894 631
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
895
896 631
        if ($rangeVariableDeclaration->isRoot) {
897 631
            $this->rootAliases[] = $dqlAlias;
898
        }
899
900 631
        $sql = $this->platform->appendLockHint(
901 631
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
902 631
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
903 631
            $this->query->getHint(Query::HINT_LOCK_MODE)
904
        );
905
906 631
        if ($class->isInheritanceTypeJoined()) {
907 90
            if ($buildNestedJoins) {
908 3
                $sql = '(' . $sql . $this->_generateClassTableInheritanceJoins($class, $dqlAlias) . ')';
909
            } else {
910 88
                $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
911
            }
912
        }
913
914 631
        return $sql;
915
    }
916
917
    /**
918
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
919
     *
920
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
921
     * @param int                            $joinType
922
     * @param AST\ConditionalExpression      $condExpr
923
     *
924
     * @return string
925
     *
926
     * @throws QueryException
927
     */
928 229
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
929
    {
930 229
        $sql = '';
931
932 229
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
933 229
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
934 229
        $indexBy                   = $joinAssociationDeclaration->indexBy;
935
936 229
        $relation        = $this->queryComponents[$joinedDqlAlias]['relation'];
937 229
        $targetClass     = $this->em->getClassMetadata($relation['targetEntity']);
938 229
        $sourceClass     = $this->em->getClassMetadata($relation['sourceEntity']);
939 229
        $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
940
941 229
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
942 229
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
943
944
        // Ensure we got the owning side, since it has all mapping info
945 229
        $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
946
947 229
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
948 3
            if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
949 2
                throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
950
            }
951
        }
952
953 227
        $targetTableJoin = null;
0 ignored issues
show
Unused Code introduced by
$targetTableJoin is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
954
955
        // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
956
        // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
957
        // The owning side is necessary at this point because only it contains the JoinColumn information.
958
        switch (true) {
959 227
            case ($assoc['type'] & ClassMetadata::TO_ONE):
960 180
                $conditions = [];
961
962 180
                foreach ($assoc['joinColumns'] as $joinColumn) {
963 180
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
964 180
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
965
966 180
                    if ($relation['isOwningSide']) {
967 104
                        $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
968
969 104
                        continue;
970
                    }
971
972 110
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
973
                }
974
975
                // Apply remaining inheritance restrictions
976 180
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
977
978 180
                if ($discrSql) {
979 3
                    $conditions[] = $discrSql;
980
                }
981
982
                // Apply the filters
983 180
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
984
985 180
                if ($filterExpr) {
986 1
                    $conditions[] = $filterExpr;
987
                }
988
989
                $targetTableJoin = [
990 180
                    'table' => $targetTableName . ' ' . $targetTableAlias,
991 180
                    'condition' => implode(' AND ', $conditions),
992
                ];
993 180
                break;
994
995 57
            case ($assoc['type'] == ClassMetadata::MANY_TO_MANY):
996
                // Join relation table
997 57
                $joinTable      = $assoc['joinTable'];
998 57
                $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
999 57
                $joinTableName  = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
1000
1001 57
                $conditions      = [];
1002 57
                $relationColumns = ($relation['isOwningSide'])
1003 48
                    ? $assoc['joinTable']['joinColumns']
1004 57
                    : $assoc['joinTable']['inverseJoinColumns'];
1005
1006 57 View Code Duplication
                foreach ($relationColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1007 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1008 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1009
1010 57
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1011
                }
1012
1013 57
                $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
1014
1015
                // Join target table
1016 57
                $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
1017
1018 57
                $conditions      = [];
1019 57
                $relationColumns = ($relation['isOwningSide'])
1020 48
                    ? $assoc['joinTable']['inverseJoinColumns']
1021 57
                    : $assoc['joinTable']['joinColumns'];
1022
1023 57 View Code Duplication
                foreach ($relationColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1024 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1025 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1026
1027 57
                    $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1028
                }
1029
1030
                // Apply remaining inheritance restrictions
1031 57
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1032
1033 57
                if ($discrSql) {
1034 1
                    $conditions[] = $discrSql;
1035
                }
1036
1037
                // Apply the filters
1038 57
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1039
1040 57
                if ($filterExpr) {
1041 1
                    $conditions[] = $filterExpr;
1042
                }
1043
1044
                $targetTableJoin = [
1045 57
                    'table' => $targetTableName . ' ' . $targetTableAlias,
1046 57
                    'condition' => implode(' AND ', $conditions),
1047
                ];
1048 57
                break;
1049
1050
            default:
1051
                throw new \BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY');
1052
        }
1053
1054
        // Handle WITH clause
1055 227
        $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1056
1057 227
        if ($targetClass->isInheritanceTypeJoined()) {
1058 9
            $ctiJoins = $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
1059
            // If we have WITH condition, we need to build nested joins for target class table and cti joins
1060 9
            if ($withCondition) {
1061 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1062
            } else {
1063 9
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1064
            }
1065
        } else {
1066 218
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1067
        }
1068
1069 227
        if ($withCondition) {
1070 5
            $sql .= ' AND ' . $withCondition;
1071
        }
1072
1073
        // Apply the indexes
1074 227
        if ($indexBy) {
1075
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1076 6
            $this->walkIndexBy($indexBy);
1077 222
        } else if (isset($relation['indexBy'])) {
1078 3
            $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
1079
        }
1080
1081 227
        return $sql;
1082
    }
1083
1084
    /**
1085
     * {@inheritdoc}
1086
     */
1087 118
    public function walkFunction($function)
1088
    {
1089 118
        return $function->getSql($this);
1090
    }
1091
1092
    /**
1093
     * {@inheritdoc}
1094
     */
1095 153
    public function walkOrderByClause($orderByClause)
1096
    {
1097 153
        $orderByItems = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
1098
1099 152
        if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
1100
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1101
        }
1102
1103 152
        return ' ORDER BY ' . implode(', ', $orderByItems);
1104
    }
1105
1106
    /**
1107
     * {@inheritdoc}
1108
     */
1109 171
    public function walkOrderByItem($orderByItem)
1110
    {
1111 171
        $type = strtoupper($orderByItem->type);
1112 171
        $expr = $orderByItem->expression;
1113 171
        $sql  = ($expr instanceof AST\Node)
1114 164
            ? $expr->dispatch($this)
1115 170
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1116
1117 170
        $this->orderedColumnsMap[$sql] = $type;
1118
1119 170
        if ($expr instanceof AST\Subselect) {
1120 2
            return '(' . $sql . ') ' . $type;
1121
        }
1122
1123 168
        return $sql . ' ' . $type;
1124
    }
1125
1126
    /**
1127
     * {@inheritdoc}
1128
     */
1129 14
    public function walkHavingClause($havingClause)
1130
    {
1131 14
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1132
    }
1133
1134
    /**
1135
     * {@inheritdoc}
1136
     */
1137 247
    public function walkJoin($join)
1138
    {
1139 247
        $joinType        = $join->joinType;
1140 247
        $joinDeclaration = $join->joinAssociationDeclaration;
1141
1142 247
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1143 57
            ? ' LEFT JOIN '
1144 247
            : ' INNER JOIN ';
1145
1146
        switch (true) {
1147 247
            case ($joinDeclaration instanceof AST\RangeVariableDeclaration):
1148 18
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1149 18
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1150 18
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1151 18
                $conditions = [];
1152
1153 18
                if ($join->conditionalExpression) {
1154 16
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1155
                }
1156
1157 18
                $isUnconditionalJoin = empty($conditions);
1158 18
                $condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER && $isUnconditionalJoin)
1159 2
                    ? ' AND '
1160 18
                    : ' ON ';
1161
1162 18
                $sql .= $this->generateRangeVariableDeclarationSQL($joinDeclaration, !$isUnconditionalJoin);
1163
1164
                // Apply remaining inheritance restrictions
1165 18
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1166
1167 18
                if ($discrSql) {
1168 3
                    $conditions[] = $discrSql;
1169
                }
1170
1171
                // Apply the filters
1172 18
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1173
1174 18
                if ($filterExpr) {
1175
                    $conditions[] = $filterExpr;
1176
                }
1177
1178 18
                if ($conditions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $conditions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1179 16
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1180
                }
1181
1182 18
                break;
1183
1184 229
            case ($joinDeclaration instanceof AST\JoinAssociationDeclaration):
1185 229
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1186 227
                break;
1187
        }
1188
1189 245
        return $sql;
1190
    }
1191
1192
    /**
1193
     * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1194
     *
1195
     * @param AST\CoalesceExpression $coalesceExpression
1196
     *
1197
     * @return string The SQL.
1198
     */
1199 2
    public function walkCoalesceExpression($coalesceExpression)
1200
    {
1201 2
        $sql = 'COALESCE(';
1202
1203 2
        $scalarExpressions = [];
1204
1205 2
        foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1206 2
            $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1207
        }
1208
1209 2
        $sql .= implode(', ', $scalarExpressions) . ')';
1210
1211 2
        return $sql;
1212
    }
1213
1214
    /**
1215
     * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1216
     *
1217
     * @param AST\NullIfExpression $nullIfExpression
1218
     *
1219
     * @return string The SQL.
1220
     */
1221 3
    public function walkNullIfExpression($nullIfExpression)
1222
    {
1223 3
        $firstExpression = is_string($nullIfExpression->firstExpression)
1224
            ? $this->conn->quote($nullIfExpression->firstExpression)
1225 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1226
1227 3
        $secondExpression = is_string($nullIfExpression->secondExpression)
1228
            ? $this->conn->quote($nullIfExpression->secondExpression)
1229 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1230
1231 3
        return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1232
    }
1233
1234
    /**
1235
     * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1236
     *
1237
     * @param AST\GeneralCaseExpression $generalCaseExpression
1238
     *
1239
     * @return string The SQL.
1240
     */
1241 9 View Code Duplication
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1242
    {
1243 9
        $sql = 'CASE';
1244
1245 9
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1246 9
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1247 9
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1248
        }
1249
1250 9
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1251
1252 9
        return $sql;
1253
    }
1254
1255
    /**
1256
     * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1257
     *
1258
     * @param AST\SimpleCaseExpression $simpleCaseExpression
1259
     *
1260
     * @return string The SQL.
1261
     */
1262 5 View Code Duplication
    public function walkSimpleCaseExpression($simpleCaseExpression)
1263
    {
1264 5
        $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1265
1266 5
        foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1267 5
            $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1268 5
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1269
        }
1270
1271 5
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1272
1273 5
        return $sql;
1274
    }
1275
1276
    /**
1277
     * {@inheritdoc}
1278
     */
1279 628
    public function walkSelectExpression($selectExpression)
1280
    {
1281 628
        $sql    = '';
1282 628
        $expr   = $selectExpression->expression;
1283 628
        $hidden = $selectExpression->hiddenAliasResultVariable;
1284
1285
        switch (true) {
1286 628
            case ($expr instanceof AST\PathExpression):
1287 102
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1288
                    throw QueryException::invalidPathExpression($expr);
1289
                }
1290
1291 102
                $fieldName = $expr->field;
1292 102
                $dqlAlias  = $expr->identificationVariable;
1293 102
                $qComp     = $this->queryComponents[$dqlAlias];
1294 102
                $class     = $qComp['metadata'];
1295
1296 102
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1297 102
                $tableName   = ($class->isInheritanceTypeJoined())
1298 11
                    ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1299 102
                    : $class->getTableName();
1300
1301 102
                $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1302 102
                $fieldMapping  = $class->fieldMappings[$fieldName];
1303 102
                $columnName    = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1304 102
                $columnAlias   = $this->getSQLColumnAlias($fieldMapping['columnName']);
1305 102
                $col           = $sqlTableAlias . '.' . $columnName;
1306
1307 102
                if (isset($fieldMapping['requireSQLConversion'])) {
1308 2
                    $type = Type::getType($fieldMapping['type']);
1309 2
                    $col  = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1310
                }
1311
1312 102
                $sql .= $col . ' AS ' . $columnAlias;
1313
1314 102
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1315
1316 102
                if ( ! $hidden) {
1317 102
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
1318 102
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1319
                }
1320
1321 102
                break;
1322
1323 577
            case ($expr instanceof AST\AggregateExpression):
1324 568
            case ($expr instanceof AST\Functions\FunctionNode):
1325 528
            case ($expr instanceof AST\SimpleArithmeticExpression):
1326 528
            case ($expr instanceof AST\ArithmeticTerm):
1327 526
            case ($expr instanceof AST\ArithmeticFactor):
1328 525
            case ($expr instanceof AST\ParenthesisExpression):
1329 524
            case ($expr instanceof AST\Literal):
1330 523
            case ($expr instanceof AST\NullIfExpression):
1331 522
            case ($expr instanceof AST\CoalesceExpression):
1332 521
            case ($expr instanceof AST\GeneralCaseExpression):
1333 517 View Code Duplication
            case ($expr instanceof AST\SimpleCaseExpression):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1334 107
                $columnAlias = $this->getSQLColumnAlias('sclr');
1335 107
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1336
1337 107
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1338
1339 107
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1340
1341 107
                if ( ! $hidden) {
1342
                    // We cannot resolve field type here; assume 'string'.
1343 107
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1344
                }
1345 107
                break;
1346
1347 516 View Code Duplication
            case ($expr instanceof AST\Subselect):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1348 15
                $columnAlias = $this->getSQLColumnAlias('sclr');
1349 15
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1350
1351 15
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1352
1353 15
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1354
1355 15
                if ( ! $hidden) {
1356
                    // We cannot resolve field type here; assume 'string'.
1357 13
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1358
                }
1359 15
                break;
1360
1361 512
            case ($expr instanceof AST\NewObjectExpression):
1362 22
                $sql .= $this->walkNewObject($expr,$selectExpression->fieldIdentificationVariable);
1363 22
                break;
1364
1365
            default:
1366
                // IdentificationVariable or PartialObjectExpression
1367 490
                if ($expr instanceof AST\PartialObjectExpression) {
1368 16
                    $dqlAlias = $expr->identificationVariable;
1369 16
                    $partialFieldSet = $expr->partialFieldSet;
1370
                } else {
1371 485
                    $dqlAlias = $expr;
1372 485
                    $partialFieldSet = [];
1373
                }
1374
1375 490
                $queryComp   = $this->queryComponents[$dqlAlias];
1376 490
                $class       = $queryComp['metadata'];
1377 490
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1378
1379 490
                if ( ! isset($this->selectedClasses[$dqlAlias])) {
1380 490
                    $this->selectedClasses[$dqlAlias] = [
1381 490
                        'class'       => $class,
1382 490
                        'dqlAlias'    => $dqlAlias,
1383 490
                        'resultAlias' => $resultAlias
1384
                    ];
1385
                }
1386
1387 490
                $sqlParts = [];
1388
1389
                // Select all fields from the queried class
1390 490 View Code Duplication
                foreach ($class->fieldMappings as $fieldName => $mapping) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1391 489
                    if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $partialFieldSet of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1392 14
                        continue;
1393
                    }
1394
1395 488
                    $tableName = (isset($mapping['inherited']))
1396 52
                        ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1397 488
                        : $class->getTableName();
1398
1399 488
                    $sqlTableAlias    = $this->getSQLTableAlias($tableName, $dqlAlias);
1400 488
                    $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1401 488
                    $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1402
1403 488
                    $col = $sqlTableAlias . '.' . $quotedColumnName;
1404
1405 488
                    if (isset($mapping['requireSQLConversion'])) {
1406 5
                        $type = Type::getType($mapping['type']);
1407 5
                        $col = $type->convertToPHPValueSQL($col, $this->platform);
1408
                    }
1409
1410 488
                    $sqlParts[] = $col . ' AS '. $columnAlias;
1411
1412 488
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1413
1414 488
                    $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
1415
                }
1416
1417
                // Add any additional fields of subclasses (excluding inherited fields)
1418
                // 1) on Single Table Inheritance: always, since its marginal overhead
1419
                // 2) on Class Table Inheritance only if partial objects are disallowed,
1420
                //    since it requires outer joining subtables.
1421 490
                if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1422 397 View Code Duplication
                    foreach ($class->subClasses as $subClassName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1423 42
                        $subClass      = $this->em->getClassMetadata($subClassName);
1424 42
                        $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
1425
1426 42
                        foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1427 42
                            if (isset($mapping['inherited']) || ($partialFieldSet && !in_array($fieldName, $partialFieldSet))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $partialFieldSet of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1428 42
                                continue;
1429
                            }
1430
1431 34
                            $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1432 34
                            $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
1433
1434 34
                            $col = $sqlTableAlias . '.' . $quotedColumnName;
1435
1436 34
                            if (isset($mapping['requireSQLConversion'])) {
1437
                                $type = Type::getType($mapping['type']);
1438
                                $col = $type->convertToPHPValueSQL($col, $this->platform);
1439
                            }
1440
1441 34
                            $sqlParts[] = $col . ' AS ' . $columnAlias;
1442
1443 34
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1444
1445 42
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1446
                        }
1447
                    }
1448
                }
1449
1450 490
                $sql .= implode(', ', $sqlParts);
1451
        }
1452
1453 628
        return $sql;
1454
    }
1455
1456
    /**
1457
     * {@inheritdoc}
1458
     */
1459
    public function walkQuantifiedExpression($qExpr)
1460
    {
1461
        return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1462
    }
1463
1464
    /**
1465
     * {@inheritdoc}
1466
     */
1467 33
    public function walkSubselect($subselect)
1468
    {
1469 33
        $useAliasesBefore  = $this->useSqlTableAliases;
1470 33
        $rootAliasesBefore = $this->rootAliases;
1471
1472 33
        $this->rootAliases = []; // reset the rootAliases for the subselect
1473 33
        $this->useSqlTableAliases = true;
1474
1475 33
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1476 33
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1477 33
        $sql .= $this->walkWhereClause($subselect->whereClause);
0 ignored issues
show
Bug introduced by
It seems like $subselect->whereClause can be null; however, walkWhereClause() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1478
1479 33
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1480 33
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1481 33
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1482
1483 33
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1484 33
        $this->useSqlTableAliases = $useAliasesBefore;
1485
1486 33
        return $sql;
1487
    }
1488
1489
    /**
1490
     * {@inheritdoc}
1491
     */
1492 33 View Code Duplication
    public function walkSubselectFromClause($subselectFromClause)
1493
    {
1494 33
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1495 33
        $sqlParts               = [];
1496
1497 33
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1498 33
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1499
        }
1500
1501 33
        return ' FROM ' . implode(', ', $sqlParts);
1502
    }
1503
1504
    /**
1505
     * {@inheritdoc}
1506
     */
1507 33
    public function walkSimpleSelectClause($simpleSelectClause)
1508
    {
1509 33
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1510 33
            . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1511
    }
1512
1513
    /**
1514
     * @param \Doctrine\ORM\Query\AST\ParenthesisExpression $parenthesisExpression
1515
     *
1516
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
1517
     */
1518 22
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1519
    {
1520 22
        return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
1521
    }
1522
1523
    /**
1524
     * @param AST\NewObjectExpression   $newObjectExpression
1525
     * @param null|string               $newObjectResultAlias
1526
     * @return string The SQL.
1527
     */
1528 22
    public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
1529
    {
1530 22
        $sqlSelectExpressions = [];
1531 22
        $objIndex             = $newObjectResultAlias?:$this->newObjectCounter++;
1532
1533 22
        foreach ($newObjectExpression->args as $argIndex => $e) {
1534 22
            $resultAlias = $this->scalarResultCounter++;
1535 22
            $columnAlias = $this->getSQLColumnAlias('sclr');
1536 22
            $fieldType   = 'string';
1537
1538
            switch (true) {
1539 22
                case ($e instanceof AST\NewObjectExpression):
1540
                    $sqlSelectExpressions[] = $e->dispatch($this);
1541
                    break;
1542
1543 22
                case ($e instanceof AST\Subselect):
1544 1
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1545 1
                    break;
1546
1547 22
                case ($e instanceof AST\PathExpression):
1548 22
                    $dqlAlias  = $e->identificationVariable;
1549 22
                    $qComp     = $this->queryComponents[$dqlAlias];
1550 22
                    $class     = $qComp['metadata'];
1551 22
                    $fieldType = $class->fieldMappings[$e->field]['type'];
1552
1553 22
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1554 22
                    break;
1555
1556 6
                case ($e instanceof AST\Literal):
1557 1
                    switch ($e->type) {
1558 1
                        case AST\Literal::BOOLEAN:
1559 1
                            $fieldType = 'boolean';
1560 1
                            break;
1561
1562 1
                        case AST\Literal::NUMERIC:
1563 1
                            $fieldType = is_float($e->value) ? 'float' : 'integer';
1564 1
                            break;
1565
                    }
1566
1567 1
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1568 1
                    break;
1569
1570
                default:
1571 5
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1572 5
                    break;
1573
            }
1574
1575 22
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1576 22
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1577
1578 22
            $this->rsm->newObjectMappings[$columnAlias] = [
1579 22
                'className' => $newObjectExpression->className,
1580 22
                'objIndex'  => $objIndex,
1581 22
                'argIndex'  => $argIndex
1582
            ];
1583
        }
1584
1585 22
        return implode(', ', $sqlSelectExpressions);
1586
    }
1587
1588
    /**
1589
     * {@inheritdoc}
1590
     */
1591 33
    public function walkSimpleSelectExpression($simpleSelectExpression)
1592
    {
1593 33
        $expr = $simpleSelectExpression->expression;
1594 33
        $sql  = ' ';
1595
1596
        switch (true) {
1597 33
            case ($expr instanceof AST\PathExpression):
1598 9
                $sql .= $this->walkPathExpression($expr);
1599 9
                break;
1600
1601 24 View Code Duplication
            case ($expr instanceof AST\Subselect):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1602
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1603
1604
                $columnAlias = 'sclr' . $this->aliasCounter++;
1605
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1606
1607
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1608
                break;
1609
1610 24
            case ($expr instanceof AST\Functions\FunctionNode):
1611 11
            case ($expr instanceof AST\SimpleArithmeticExpression):
1612 10
            case ($expr instanceof AST\ArithmeticTerm):
1613 9
            case ($expr instanceof AST\ArithmeticFactor):
1614 9
            case ($expr instanceof AST\Literal):
1615 7
            case ($expr instanceof AST\NullIfExpression):
1616 7
            case ($expr instanceof AST\CoalesceExpression):
1617 7
            case ($expr instanceof AST\GeneralCaseExpression):
1618 5 View Code Duplication
            case ($expr instanceof AST\SimpleCaseExpression):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1619 21
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1620
1621 21
                $columnAlias = $this->getSQLColumnAlias('sclr');
1622 21
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1623
1624 21
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1625 21
                break;
1626
1627 3
            case ($expr instanceof AST\ParenthesisExpression):
1628 1
                $sql .= $this->walkParenthesisExpression($expr);
1629 1
                break;
1630
1631
            default: // IdentificationVariable
1632 2
                $sql .= $this->walkEntityIdentificationVariable($expr);
1633 2
                break;
1634
        }
1635
1636 33
        return $sql;
1637
    }
1638
1639
    /**
1640
     * {@inheritdoc}
1641
     */
1642 76
    public function walkAggregateExpression($aggExpression)
1643
    {
1644 76
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1645 76
            . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
0 ignored issues
show
Bug introduced by
It seems like $aggExpression->pathExpression can also be of type object<Doctrine\ORM\Query\AST\PathExpression>; however, Doctrine\ORM\Query\SqlWa...eArithmeticExpression() does only seem to accept object<Doctrine\ORM\Quer...leArithmeticExpression>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1646
    }
1647
1648
    /**
1649
     * {@inheritdoc}
1650
     */
1651 24
    public function walkGroupByClause($groupByClause)
1652
    {
1653 24
        $sqlParts = [];
1654
1655 24
        foreach ($groupByClause->groupByItems as $groupByItem) {
1656 24
            $sqlParts[] = $this->walkGroupByItem($groupByItem);
1657
        }
1658
1659 24
        return ' GROUP BY ' . implode(', ', $sqlParts);
1660
    }
1661
1662
    /**
1663
     * {@inheritdoc}
1664
     */
1665 24
    public function walkGroupByItem($groupByItem)
1666
    {
1667
        // StateFieldPathExpression
1668 24
        if ( ! is_string($groupByItem)) {
1669 11
            return $this->walkPathExpression($groupByItem);
1670
        }
1671
1672
        // ResultVariable
1673 13
        if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1674 2
            $resultVariable = $this->queryComponents[$groupByItem]['resultVariable'];
1675
1676 2
            if ($resultVariable instanceof AST\PathExpression) {
1677 1
                return $this->walkPathExpression($resultVariable);
1678
            }
1679
1680 1
            if (isset($resultVariable->pathExpression)) {
1681
                return $this->walkPathExpression($resultVariable->pathExpression);
1682
            }
1683
1684 1
            return $this->walkResultVariable($groupByItem);
1685
        }
1686
1687
        // IdentificationVariable
1688 11
        $sqlParts = [];
1689
1690 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->fieldNames as $field) {
1691 11
            $item       = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
1692 11
            $item->type = AST\PathExpression::TYPE_STATE_FIELD;
1693
1694 11
            $sqlParts[] = $this->walkPathExpression($item);
1695
        }
1696
1697 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->associationMappings as $mapping) {
1698 11
            if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
1699 7
                $item       = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
1700 7
                $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1701
1702 11
                $sqlParts[] = $this->walkPathExpression($item);
1703
            }
1704
        }
1705
1706 11
        return implode(', ', $sqlParts);
1707
    }
1708
1709
    /**
1710
     * {@inheritdoc}
1711
     */
1712 36
    public function walkDeleteClause(AST\DeleteClause $deleteClause)
1713
    {
1714 36
        $class     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1715 36
        $tableName = $class->getTableName();
1716 36
        $sql       = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform);
1717
1718 36
        $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1719 36
        $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1720
1721 36
        return $sql;
1722
    }
1723
1724
    /**
1725
     * {@inheritdoc}
1726
     */
1727 25
    public function walkUpdateClause($updateClause)
1728
    {
1729 25
        $class     = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1730 25
        $tableName = $class->getTableName();
1731 25
        $sql       = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform);
1732
1733 25
        $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1734 25
        $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1735
1736 25
        $sql .= ' SET ' . implode(', ', array_map([$this, 'walkUpdateItem'], $updateClause->updateItems));
1737
1738 25
        return $sql;
1739
    }
1740
1741
    /**
1742
     * {@inheritdoc}
1743
     */
1744 29
    public function walkUpdateItem($updateItem)
1745
    {
1746 29
        $useTableAliasesBefore = $this->useSqlTableAliases;
1747 29
        $this->useSqlTableAliases = false;
1748
1749 29
        $sql      = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1750 29
        $newValue = $updateItem->newValue;
1751
1752
        switch (true) {
1753 29
            case ($newValue instanceof AST\Node):
1754 28
                $sql .= $newValue->dispatch($this);
1755 28
                break;
1756
1757 1
            case ($newValue === null):
1758 1
                $sql .= 'NULL';
1759 1
                break;
1760
1761
            default:
1762
                $sql .= $this->conn->quote($newValue);
1763
                break;
1764
        }
1765
1766 29
        $this->useSqlTableAliases = $useTableAliasesBefore;
1767
1768 29
        return $sql;
1769
    }
1770
1771
    /**
1772
     * {@inheritdoc}
1773
     */
1774 685
    public function walkWhereClause($whereClause)
1775
    {
1776 685
        $condSql  = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1777 682
        $discrSql = $this->_generateDiscriminatorColumnConditionSQL($this->rootAliases);
1778
1779 682
        if ($this->em->hasFilters()) {
1780 43
            $filterClauses = [];
1781 43
            foreach ($this->rootAliases as $dqlAlias) {
1782 43
                $class = $this->queryComponents[$dqlAlias]['metadata'];
1783 43
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1784
1785 43
                if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) {
1786 43
                    $filterClauses[] = $filterExpr;
1787
                }
1788
            }
1789
1790 43
            if (count($filterClauses)) {
1791 6
                if ($condSql) {
1792 2
                    $condSql = '(' . $condSql . ') AND ';
1793
                }
1794
1795 6
                $condSql .= implode(' AND ', $filterClauses);
1796
            }
1797
        }
1798
1799 682
        if ($condSql) {
1800 334
            return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1801
        }
1802
1803 424
        if ($discrSql) {
1804 23
            return ' WHERE ' . $discrSql;
1805
        }
1806
1807 404
        return '';
1808
    }
1809
1810
    /**
1811
     * {@inheritdoc}
1812
     */
1813 366
    public function walkConditionalExpression($condExpr)
1814
    {
1815
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1816
        // if only one ConditionalTerm is defined
1817 366
        if ( ! ($condExpr instanceof AST\ConditionalExpression)) {
1818 311
            return $this->walkConditionalTerm($condExpr);
1819
        }
1820
1821 72
        return implode(' OR ', array_map([$this, 'walkConditionalTerm'], $condExpr->conditionalTerms));
1822
    }
1823
1824
    /**
1825
     * {@inheritdoc}
1826
     */
1827 366
    public function walkConditionalTerm($condTerm)
1828
    {
1829
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1830
        // if only one ConditionalFactor is defined
1831 366
        if ( ! ($condTerm instanceof AST\ConditionalTerm)) {
1832 299
            return $this->walkConditionalFactor($condTerm);
1833
        }
1834
1835 90
        return implode(' AND ', array_map([$this, 'walkConditionalFactor'], $condTerm->conditionalFactors));
1836
    }
1837
1838
    /**
1839
     * {@inheritdoc}
1840
     */
1841 366
    public function walkConditionalFactor($factor)
1842
    {
1843
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1844
        // if only one ConditionalPrimary is defined
1845 366
        return ( ! ($factor instanceof AST\ConditionalFactor))
1846 363
            ? $this->walkConditionalPrimary($factor)
1847 363
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1848
    }
1849
1850
    /**
1851
     * {@inheritdoc}
1852
     */
1853 366
    public function walkConditionalPrimary($primary)
1854
    {
1855 366
        if ($primary->isSimpleConditionalExpression()) {
1856 366
            return $primary->simpleConditionalExpression->dispatch($this);
1857
        }
1858
1859 25
        if ($primary->isConditionalExpression()) {
1860 25
            $condExpr = $primary->conditionalExpression;
1861
1862 25
            return '(' . $this->walkConditionalExpression($condExpr) . ')';
0 ignored issues
show
Bug introduced by
It seems like $condExpr defined by $primary->conditionalExpression on line 1860 can be null; however, Doctrine\ORM\Query\SqlWa...ConditionalExpression() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1863
        }
1864
    }
1865
1866
    /**
1867
     * {@inheritdoc}
1868
     */
1869 5
    public function walkExistsExpression($existsExpr)
1870
    {
1871 5
        $sql = ($existsExpr->not) ? 'NOT ' : '';
1872
1873 5
        $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1874
1875 5
        return $sql;
1876
    }
1877
1878
    /**
1879
     * {@inheritdoc}
1880
     */
1881 6
    public function walkCollectionMemberExpression($collMemberExpr)
1882
    {
1883 6
        $sql = $collMemberExpr->not ? 'NOT ' : '';
1884 6
        $sql .= 'EXISTS (SELECT 1 FROM ';
1885
1886 6
        $entityExpr   = $collMemberExpr->entityExpression;
1887 6
        $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1888
1889 6
        $fieldName = $collPathExpr->field;
1890 6
        $dqlAlias  = $collPathExpr->identificationVariable;
1891
1892 6
        $class = $this->queryComponents[$dqlAlias]['metadata'];
1893
1894
        switch (true) {
1895
            // InputParameter
1896 6
            case ($entityExpr instanceof AST\InputParameter):
1897 4
                $dqlParamKey = $entityExpr->name;
1898 4
                $entitySql   = '?';
1899 4
                break;
1900
1901
            // SingleValuedAssociationPathExpression | IdentificationVariable
1902 2
            case ($entityExpr instanceof AST\PathExpression):
1903 2
                $entitySql = $this->walkPathExpression($entityExpr);
1904 2
                break;
1905
1906
            default:
1907
                throw new \BadMethodCallException("Not implemented");
1908
        }
1909
1910 6
        $assoc = $class->associationMappings[$fieldName];
1911
1912 6
        if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
1913 1
            $targetClass      = $this->em->getClassMetadata($assoc['targetEntity']);
1914 1
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1915 1
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1916
1917 1
            $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE ';
1918
1919 1
            $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
1920 1
            $sqlParts    = [];
1921
1922 1 View Code Duplication
            foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1923 1
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform);
1924
1925 1
                $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
1926
            }
1927
1928 1 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1929 1
                if (isset($dqlParamKey)) {
1930 1
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1931
                }
1932
1933 1
                $sqlParts[] = $targetTableAlias . '.'  . $targetColumnName . ' = ' . $entitySql;
1934
            }
1935
1936 1
            $sql .= implode(' AND ', $sqlParts);
1937
        } else { // many-to-many
1938 5
            $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
1939
1940 5
            $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
1941 5
            $joinTable = $owningAssoc['joinTable'];
1942
1943
            // SQL table aliases
1944 5
            $joinTableAlias   = $this->getSQLTableAlias($joinTable['name']);
1945 5
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1946 5
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1947
1948
            // join to target table
1949 5
            $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias
1950 5
                . ' INNER JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' ON ';
1951
1952
            // join conditions
1953 5
            $joinColumns  = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns'];
1954 5
            $joinSqlParts = [];
1955
1956 5 View Code Duplication
            foreach ($joinColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1957 5
                $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform);
1958
1959 5
                $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn;
1960
            }
1961
1962 5
            $sql .= implode(' AND ', $joinSqlParts);
1963 5
            $sql .= ' WHERE ';
1964
1965 5
            $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
1966 5
            $sqlParts    = [];
1967
1968 5 View Code Duplication
            foreach ($joinColumns as $joinColumn) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1969 5
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform);
1970
1971 5
                $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn;
1972
            }
1973
1974 5 View Code Duplication
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1975 5
                if (isset($dqlParamKey)) {
1976 3
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1977
                }
1978
1979 5
                $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' IN (' . $entitySql . ')';
1980
            }
1981
1982 5
            $sql .= implode(' AND ', $sqlParts);
1983
        }
1984
1985 6
        return $sql . ')';
1986
    }
1987
1988
    /**
1989
     * {@inheritdoc}
1990
     */
1991 3
    public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
1992
    {
1993 3
        $sizeFunc = new AST\Functions\SizeFunction('size');
1994 3
        $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
1995
1996 3
        return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
1997
    }
1998
1999
    /**
2000
     * {@inheritdoc}
2001
     */
2002 11
    public function walkNullComparisonExpression($nullCompExpr)
2003
    {
2004 11
        $expression = $nullCompExpr->expression;
2005 11
        $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
2006
2007
        // Handle ResultVariable
2008 11
        if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
2009 2
            return $this->walkResultVariable($expression) . $comparison;
2010
        }
2011
2012
        // Handle InputParameter mapping inclusion to ParserResult
2013 9
        if ($expression instanceof AST\InputParameter) {
2014
            return $this->walkInputParameter($expression) . $comparison;
2015
        }
2016
2017 9
        return $expression->dispatch($this) . $comparison;
2018
    }
2019
2020
    /**
2021
     * {@inheritdoc}
2022
     */
2023 85
    public function walkInExpression($inExpr)
2024
    {
2025 85
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2026
2027 84
        $sql .= ($inExpr->subselect)
2028 7
            ? $this->walkSubselect($inExpr->subselect)
2029 84
            : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals));
2030
2031 84
        $sql .= ')';
2032
2033 84
        return $sql;
2034
    }
2035
2036
    /**
2037
     * {@inheritdoc}
2038
     */
2039 9
    public function walkInstanceOfExpression($instanceOfExpr)
2040
    {
2041 9
        $sql = '';
2042
2043 9
        $dqlAlias = $instanceOfExpr->identificationVariable;
2044 9
        $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata'];
2045
2046 9
        if ($class->discriminatorColumn) {
2047 9
            $discrClass = $this->em->getClassMetadata($class->rootEntityName);
2048
        }
2049
2050 9
        if ($this->useSqlTableAliases) {
2051 9
            $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
2052
        }
2053
2054 9
        $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
2055
2056 9
        $sqlParameterList = [];
2057
2058 9
        foreach ($instanceOfExpr->value as $parameter) {
2059 9
            if ($parameter instanceof AST\InputParameter) {
2060 3
                $this->rsm->addMetadataParameterMapping($parameter->name, 'discriminatorValue');
2061
2062 3
                $sqlParameterList[] = $this->walkInputParameter($parameter);
2063
2064 3
                continue;
2065
            }
2066
2067
            // Get name from ClassMetadata to resolve aliases.
2068 6
            $entityClassName    = $this->em->getClassMetadata($parameter)->name;
2069 6
            $discriminatorValue = $class->discriminatorValue;
2070
2071 6
            if ($entityClassName !== $class->name) {
2072 5
                $discrMap = array_flip($class->discriminatorMap);
2073
2074 5
                if ( ! isset($discrMap[$entityClassName])) {
2075 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
2076
                }
2077
2078 4
                $discriminatorValue = $discrMap[$entityClassName];
2079
            }
2080
2081 5
            $sqlParameterList[] = $this->conn->quote($discriminatorValue);
2082
        }
2083
2084 8
        $sql .= '(' . implode(', ', $sqlParameterList) . ')';
2085
2086 8
        return $sql;
2087
    }
2088
2089
    /**
2090
     * {@inheritdoc}
2091
     */
2092 77
    public function walkInParameter($inParam)
2093
    {
2094 77
        return $inParam instanceof AST\InputParameter
2095 68
            ? $this->walkInputParameter($inParam)
2096 77
            : $this->walkLiteral($inParam);
2097
    }
2098
2099
    /**
2100
     * {@inheritdoc}
2101
     */
2102 148
    public function walkLiteral($literal)
2103
    {
2104 148
        switch ($literal->type) {
2105 148
            case AST\Literal::STRING:
2106 50
                return $this->conn->quote($literal->value);
2107
2108 111
            case AST\Literal::BOOLEAN:
2109 8
                return $this->conn->getDatabasePlatform()->convertBooleans('true' === strtolower($literal->value));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->conn->getD...ower($literal->value)); (object|integer|double|string|null|array) is incompatible with the return type declared by the interface Doctrine\ORM\Query\TreeWalker::walkLiteral of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2110
2111 104
            case AST\Literal::NUMERIC:
2112 104
                return $literal->value;
2113
2114
            default:
2115
                throw QueryException::invalidLiteral($literal);
2116
        }
2117
    }
2118
2119
    /**
2120
     * {@inheritdoc}
2121
     */
2122 6
    public function walkBetweenExpression($betweenExpr)
2123
    {
2124 6
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2125
2126 6
        if ($betweenExpr->not) {
2127 2
            $sql .= ' NOT';
2128
        }
2129
2130 6
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2131 6
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2132
2133 6
        return $sql;
2134
    }
2135
2136
    /**
2137
     * {@inheritdoc}
2138
     */
2139 9
    public function walkLikeExpression($likeExpr)
2140
    {
2141 9
        $stringExpr = $likeExpr->stringExpression;
2142 9
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2143 1
            ? $this->walkResultVariable($stringExpr)
2144 9
            : $stringExpr->dispatch($this);
2145
2146 9
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2147
2148 9
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2149 4
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2150 6
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2151 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2152 6
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2153 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2154
        } else {
2155 6
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2156
        }
2157
2158 9
        if ($likeExpr->escapeChar) {
2159 1
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2160
        }
2161
2162 9
        return $sql;
2163
    }
2164
2165
    /**
2166
     * {@inheritdoc}
2167
     */
2168 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2169
    {
2170 5
        return $this->walkPathExpression($stateFieldPathExpression);
2171
    }
2172
2173
    /**
2174
     * {@inheritdoc}
2175
     */
2176 263
    public function walkComparisonExpression($compExpr)
2177
    {
2178 263
        $leftExpr  = $compExpr->leftExpression;
2179 263
        $rightExpr = $compExpr->rightExpression;
2180 263
        $sql       = '';
2181
2182 263
        $sql .= ($leftExpr instanceof AST\Node)
2183 263
            ? $leftExpr->dispatch($this)
2184 262
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2185
2186 262
        $sql .= ' ' . $compExpr->operator . ' ';
2187
2188 262
        $sql .= ($rightExpr instanceof AST\Node)
2189 260
            ? $rightExpr->dispatch($this)
2190 262
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2191
2192 262
        return $sql;
2193
    }
2194
2195
    /**
2196
     * {@inheritdoc}
2197
     */
2198 217
    public function walkInputParameter($inputParam)
2199
    {
2200 217
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2201
2202 217
        $parameter = $this->query->getParameter($inputParam->name);
2203
2204 217
        if ($parameter && Type::hasType($type = $parameter->getType())) {
2205 56
            return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2206
        }
2207
2208 167
        return '?';
2209
    }
2210
2211
    /**
2212
     * {@inheritdoc}
2213
     */
2214 329
    public function walkArithmeticExpression($arithmeticExpr)
2215
    {
2216 329
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2217 329
            ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
0 ignored issues
show
Bug introduced by
It seems like $arithmeticExpr->simpleArithmeticExpression can be null; however, walkSimpleArithmeticExpression() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2218 327
            : '(' . $this->walkSubselect($arithmeticExpr->subselect) . ')';
0 ignored issues
show
Bug introduced by
It seems like $arithmeticExpr->subselect can be null; however, walkSubselect() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2219
    }
2220
2221
    /**
2222
     * {@inheritdoc}
2223
     */
2224 392
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2225
    {
2226 392
        if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2227 342
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2228
        }
2229
2230 75
        return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms));
2231
    }
2232
2233
    /**
2234
     * {@inheritdoc}
2235
     */
2236 413
    public function walkArithmeticTerm($term)
2237
    {
2238 413 View Code Duplication
        if (is_string($term)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2239 21
            return (isset($this->queryComponents[$term]))
2240 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2241 21
                : $term;
2242
        }
2243
2244
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2245
        // if only one ArithmeticFactor is defined
2246 412
        if ( ! ($term instanceof AST\ArithmeticTerm)) {
2247 390
            return $this->walkArithmeticFactor($term);
2248
        }
2249
2250 47
        return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors));
2251
    }
2252
2253
    /**
2254
     * {@inheritdoc}
2255
     */
2256 413
    public function walkArithmeticFactor($factor)
2257
    {
2258 413 View Code Duplication
        if (is_string($factor)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2259 47
            return (isset($this->queryComponents[$factor]))
2260 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2261 47
                : $factor;
2262
        }
2263
2264
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2265
        // if only one ArithmeticPrimary is defined
2266 413
        if ( ! ($factor instanceof AST\ArithmeticFactor)) {
2267 412
            return $this->walkArithmeticPrimary($factor);
2268
        }
2269
2270 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2271
2272 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2273
    }
2274
2275
    /**
2276
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2277
     *
2278
     * @param mixed $primary
2279
     *
2280
     * @return string The SQL.
2281
     */
2282 413
    public function walkArithmeticPrimary($primary)
2283
    {
2284 413
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2285
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2286
        }
2287
2288 413
        if ($primary instanceof AST\Node) {
2289 413
            return $primary->dispatch($this);
2290
        }
2291
2292
        return $this->walkEntityIdentificationVariable($primary);
2293
    }
2294
2295
    /**
2296
     * {@inheritdoc}
2297
     */
2298 18
    public function walkStringPrimary($stringPrimary)
2299
    {
2300 18
        return (is_string($stringPrimary))
2301
            ? $this->conn->quote($stringPrimary)
2302 18
            : $stringPrimary->dispatch($this);
2303
    }
2304
2305
    /**
2306
     * {@inheritdoc}
2307
     */
2308 30
    public function walkResultVariable($resultVariable)
2309
    {
2310 30
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2311
2312 30
        if (is_array($resultAlias)) {
2313 1
            return implode(', ', $resultAlias);
2314
        }
2315
2316 29
        return $resultAlias;
2317
    }
2318
}
2319