Passed
Push — master ( 33cee1...9b9128 )
by Marco
18:20 queued 06:56
created

SqlWalker::walkPathExpression()   B

Complexity

Conditions 8
Paths 11

Size

Total Lines 53
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 8.0029

Importance

Changes 0
Metric Value
dl 0
loc 53
ccs 27
cts 28
cp 0.9643
rs 7.1199
c 0
b 0
f 0
cc 8
eloc 29
nc 11
nop 1
crap 8.0029

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 689
    public function __construct($query, $parserResult, array $queryComponents)
182
    {
183 689
        $this->query            = $query;
184 689
        $this->parserResult     = $parserResult;
185 689
        $this->queryComponents  = $queryComponents;
186 689
        $this->rsm              = $parserResult->getResultSetMapping();
187 689
        $this->em               = $query->getEntityManager();
188 689
        $this->conn             = $this->em->getConnection();
189 689
        $this->platform         = $this->conn->getDatabasePlatform();
190 689
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
191 689
    }
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 683
    public function getExecutor($AST)
261
    {
262
        switch (true) {
263 683
            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 649
            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 624
                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 635
    public function getSQLTableAlias($tableName, $dqlAlias = '')
291
    {
292 635
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
293
294 635
        if ( ! isset($this->tableAliasMap[$tableName])) {
295 635
            $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
296 635
                . $this->tableAliasCounter++ . '_';
297
        }
298
299 635
        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 624
    public function getSQLColumnAlias($columnName)
329
    {
330 624
        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 93
    private function _generateClassTableInheritanceJoins($class, $dqlAlias)
343
    {
344 93
        $sql = '';
345
346 93
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
347
348
        // INNER JOIN parent class tables
349 93
        foreach ($class->parentClasses as $parentClassName) {
350 66
            $parentClass = $this->em->getClassMetadata($parentClassName);
351 66
            $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 66
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
355 66
            $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
356
357 66
            $sqlParts = [];
358
359 66 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 66
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
361
            }
362
363
            // Add filters on the root class
364 66
            if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
365 1
                $sqlParts[] = $filterSql;
366
            }
367
368 66
            $sql .= implode(' AND ', $sqlParts);
369
        }
370
371
        // Ignore subclassing inclusion if partial objects is disallowed
372 93
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
373 21
            return $sql;
374
        }
375
376
        // LEFT JOIN child class tables
377 72
        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 72
        return $sql;
393
    }
394
395
    /**
396
     * @return string
397
     */
398 618
    private function _generateOrderedCollectionOrderByItems()
399
    {
400 618
        $orderedColumns = [];
401
402 618
        foreach ($this->selectedClasses as $selectedClass) {
403 481
            $dqlAlias  = $selectedClass['dqlAlias'];
404 481
            $qComp     = $this->queryComponents[$dqlAlias];
405
406 481
            if ( ! isset($qComp['relation']['orderBy'])) {
407 481
                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 618
        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 678
    private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
441
    {
442 678
        $sqlParts = [];
443
444 678
        foreach ($dqlAliases as $dqlAlias) {
445 678
            $class = $this->queryComponents[$dqlAlias]['metadata'];
446
447 678
            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 678
        $sql = implode(' AND ', $sqlParts);
468
469 678
        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 319
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
481
    {
482 319
        if (!$this->em->hasFilters()) {
483 281
            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 9
                $filterClauses[] = '(' . $filterExpr . ')';
510
            }
511
        }
512
513 43
        return implode(' AND ', $filterClauses);
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     */
519 624
    public function walkSelectStatement(AST\SelectStatement $AST)
520
    {
521 624
        $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 624
        $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 624
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
524 624
        $sql      = $this->walkSelectClause($AST->selectClause)
525 624
            . $this->walkFromClause($AST->fromClause)
526 622
            . $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 619
        if ($AST->groupByClause) {
529 23
            $sql .= $this->walkGroupByClause($AST->groupByClause);
530
        }
531
532 619
        if ($AST->havingClause) {
533 14
            $sql .= $this->walkHavingClause($AST->havingClause);
534
        }
535
536 619
        if ($AST->orderByClause) {
537 142
            $sql .= $this->walkOrderByClause($AST->orderByClause);
538
        }
539
540 618
        if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) {
541 6
            $sql .= ' ORDER BY ' . $orderBySql;
542
        }
543
544 618 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 618
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
549 613
            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 423
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
627
    {
628 423
        $class = $this->queryComponents[$identificationVariable]['metadata'];
629
630
        if (
631 423
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
632 54
            isset($class->fieldMappings[$fieldName]['inherited'])
633
        ) {
634 37
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
635
        }
636
637 423
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
638
    }
639
640
    /**
641
     * {@inheritdoc}
642
     */
643 492
    public function walkPathExpression($pathExpr)
644
    {
645 492
        $sql = '';
646
647
        /* @var $pathExpr Query\AST\PathExpression */
648 492
        switch ($pathExpr->type) {
649 492
            case AST\PathExpression::TYPE_STATE_FIELD:
650 471
                $fieldName = $pathExpr->field;
651 471
                $dqlAlias = $pathExpr->identificationVariable;
652 471
                $class = $this->queryComponents[$dqlAlias]['metadata'];
653
654 471
                if ($this->useSqlTableAliases) {
655 423
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
656
                }
657
658 471
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
659 471
                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 489
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 624
    public function walkSelectClause($selectClause)
701
    {
702 624
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
703 624
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
704
705 624
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
706 1
            $this->query->setHint(self::HINT_DISTINCT, true);
707
        }
708
709 624
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
710 461
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
711
            ||
712 290
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
713 624
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
714
715 624
        foreach ($this->selectedClasses as $selectedClass) {
716 487
            $class       = $selectedClass['class'];
717 487
            $dqlAlias    = $selectedClass['dqlAlias'];
718 487
            $resultAlias = $selectedClass['resultAlias'];
719
720
            // Register as entity or joined entity result
721 487
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
722 487
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
723
            } else {
724 157
                $this->rsm->addJoinedEntityResult(
725 157
                    $class->name,
726
                    $dqlAlias,
727 157
                    $this->queryComponents[$dqlAlias]['parent'],
728 157
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
729
                );
730
            }
731
732 487
            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 487
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
747 181
                continue;
748
            }
749
750
            // Add foreign key columns of class and also parent classes
751 356
            foreach ($class->associationMappings as $assoc) {
752 317
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
753 266
                    continue;
754 284
                } else if ( !$addMetaColumns && !isset($assoc['id'])) {
755
                    continue;
756
                }
757
758 284
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
759 284
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
760 284
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
761 284
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
762
763 284 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 284
                    $columnName  = $joinColumn['name'];
765 284
                    $columnAlias = $this->getSQLColumnAlias($columnName);
766 284
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
767
768 284
                    $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
769 284
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
770
771 284
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
772
                }
773
            }
774
775
            // Add foreign key columns to SQL, if necessary
776 356
            if ( ! $addMetaColumns) {
777 8
                continue;
778
            }
779
780
            // Add foreign key columns of subclasses
781 351
            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 351
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
801
                        }
802
                    }
803
                }
804
            }
805
        }
806
807 624
        $sql .= implode(', ', $sqlSelectExpressions);
808
809 624
        return $sql;
810
    }
811
812
    /**
813
     * {@inheritdoc}
814
     */
815 626 View Code Duplication
    public function walkFromClause($fromClause)
816
    {
817 626
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
818 626
        $sqlParts = [];
819
820 626
        foreach ($identificationVarDecls as $identificationVariableDecl) {
821 626
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
822
        }
823
824 624
        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 627
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
835
    {
836 627
        $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 627
        if ($identificationVariableDecl->indexBy) {
839 5
            $this->walkIndexBy($identificationVariableDecl->indexBy);
840
        }
841
842 627
        foreach ($identificationVariableDecl->joins as $join) {
843 244
            $sql .= $this->walkJoin($join);
844
        }
845
846 625
        return $sql;
847
    }
848
849
    /**
850
     * Walks down a IndexBy AST node.
851
     *
852
     * @param AST\IndexBy $indexBy
853
     *
854
     * @return void
855
     */
856 8
    public function walkIndexBy($indexBy)
857
    {
858 8
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
859 8
        $alias          = $pathExpression->identificationVariable;
860 8
        $field          = $pathExpression->field;
861
862 8
        if (isset($this->scalarFields[$alias][$field])) {
863
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
864
865
            return;
866
        }
867
868 8
        $this->rsm->addIndexBy($alias, $field);
869 8
    }
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 627
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
879
    {
880 627
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
881 627
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
882
883 627
        if ($rangeVariableDeclaration->isRoot) {
884 627
            $this->rootAliases[] = $dqlAlias;
885
        }
886
887 627
        $sql = $this->platform->appendLockHint(
888 627
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
889 627
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
890 627
            $this->query->getHint(Query::HINT_LOCK_MODE)
891
        );
892
893 627
        if ($class->isInheritanceTypeJoined()) {
894 89
            $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
895
        }
896
897 627
        return $sql;
898
    }
899
900
    /**
901
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
902
     *
903
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
904
     * @param int                            $joinType
905
     * @param AST\ConditionalExpression      $condExpr
906
     *
907
     * @return string
908
     *
909
     * @throws QueryException
910
     */
911 227
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
912
    {
913 227
        $sql = '';
914
915 227
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
916 227
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
917 227
        $indexBy                   = $joinAssociationDeclaration->indexBy;
918
919 227
        $relation        = $this->queryComponents[$joinedDqlAlias]['relation'];
920 227
        $targetClass     = $this->em->getClassMetadata($relation['targetEntity']);
921 227
        $sourceClass     = $this->em->getClassMetadata($relation['sourceEntity']);
922 227
        $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
923
924 227
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
925 227
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
926
927
        // Ensure we got the owning side, since it has all mapping info
928 227
        $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
929
930 227
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
931 3
            if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
932 2
                throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
933
            }
934
        }
935
936 225
        $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...
937
938
        // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
939
        // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
940
        // The owning side is necessary at this point because only it contains the JoinColumn information.
941
        switch (true) {
942 225
            case ($assoc['type'] & ClassMetadata::TO_ONE):
943 178
                $conditions = [];
944
945 178
                foreach ($assoc['joinColumns'] as $joinColumn) {
946 178
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
947 178
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
948
949 178
                    if ($relation['isOwningSide']) {
950 103
                        $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
951
952 103
                        continue;
953
                    }
954
955 108
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
956
                }
957
958
                // Apply remaining inheritance restrictions
959 178
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
960
961 178
                if ($discrSql) {
962 3
                    $conditions[] = $discrSql;
963
                }
964
965
                // Apply the filters
966 178
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
967
968 178
                if ($filterExpr) {
969 1
                    $conditions[] = $filterExpr;
970
                }
971
972
                $targetTableJoin = [
973 178
                    'table' => $targetTableName . ' ' . $targetTableAlias,
974 178
                    'condition' => implode(' AND ', $conditions),
975
                ];
976 178
                break;
977
978 57
            case ($assoc['type'] == ClassMetadata::MANY_TO_MANY):
979
                // Join relation table
980 57
                $joinTable      = $assoc['joinTable'];
981 57
                $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
982 57
                $joinTableName  = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
983
984 57
                $conditions      = [];
985 57
                $relationColumns = ($relation['isOwningSide'])
986 48
                    ? $assoc['joinTable']['joinColumns']
987 57
                    : $assoc['joinTable']['inverseJoinColumns'];
988
989 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...
990 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
991 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
992
993 57
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
994
                }
995
996 57
                $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
997
998
                // Join target table
999 57
                $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
1000
1001 57
                $conditions      = [];
1002 57
                $relationColumns = ($relation['isOwningSide'])
1003 48
                    ? $assoc['joinTable']['inverseJoinColumns']
1004 57
                    : $assoc['joinTable']['joinColumns'];
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[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1011
                }
1012
1013
                // Apply remaining inheritance restrictions
1014 57
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1015
1016 57
                if ($discrSql) {
1017 1
                    $conditions[] = $discrSql;
1018
                }
1019
1020
                // Apply the filters
1021 57
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1022
1023 57
                if ($filterExpr) {
1024 1
                    $conditions[] = $filterExpr;
1025
                }
1026
1027
                $targetTableJoin = [
1028 57
                    'table' => $targetTableName . ' ' . $targetTableAlias,
1029 57
                    'condition' => implode(' AND ', $conditions),
1030
                ];
1031 57
                break;
1032
1033
            default:
1034
                throw new \BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY');
1035
        }
1036
1037
        // Handle WITH clause
1038 225
        $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1039
1040 225
        if ($targetClass->isInheritanceTypeJoined()) {
1041 9
            $ctiJoins = $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
1042
            // If we have WITH condition, we need to build nested joins for target class table and cti joins
1043 9
            if ($withCondition) {
1044 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1045
            } else {
1046 8
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1047
            }
1048
        } else {
1049 216
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1050
        }
1051
1052 225
        if ($withCondition) {
1053 5
            $sql .= ' AND ' . $withCondition;
1054
        }
1055
1056
        // Apply the indexes
1057 225
        if ($indexBy) {
1058
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1059 5
            $this->walkIndexBy($indexBy);
1060 220
        } else if (isset($relation['indexBy'])) {
1061 3
            $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
1062
        }
1063
1064 225
        return $sql;
1065
    }
1066
1067
    /**
1068
     * {@inheritdoc}
1069
     */
1070 118
    public function walkFunction($function)
1071
    {
1072 118
        return $function->getSql($this);
1073
    }
1074
1075
    /**
1076
     * {@inheritdoc}
1077
     */
1078 153
    public function walkOrderByClause($orderByClause)
1079
    {
1080 153
        $orderByItems = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
1081
1082 152
        if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
1083
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1084
        }
1085
1086 152
        return ' ORDER BY ' . implode(', ', $orderByItems);
1087
    }
1088
1089
    /**
1090
     * {@inheritdoc}
1091
     */
1092 171
    public function walkOrderByItem($orderByItem)
1093
    {
1094 171
        $type = strtoupper($orderByItem->type);
1095 171
        $expr = $orderByItem->expression;
1096 171
        $sql  = ($expr instanceof AST\Node)
1097 164
            ? $expr->dispatch($this)
1098 170
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1099
1100 170
        $this->orderedColumnsMap[$sql] = $type;
1101
1102 170
        if ($expr instanceof AST\Subselect) {
1103 2
            return '(' . $sql . ') ' . $type;
1104
        }
1105
1106 168
        return $sql . ' ' . $type;
1107
    }
1108
1109
    /**
1110
     * {@inheritdoc}
1111
     */
1112 14
    public function walkHavingClause($havingClause)
1113
    {
1114 14
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1115
    }
1116
1117
    /**
1118
     * {@inheritdoc}
1119
     */
1120 244
    public function walkJoin($join)
1121
    {
1122 244
        $joinType        = $join->joinType;
1123 244
        $joinDeclaration = $join->joinAssociationDeclaration;
1124
1125 244
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1126 56
            ? ' LEFT JOIN '
1127 244
            : ' INNER JOIN ';
1128
1129
        switch (true) {
1130 244
            case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration):
1131 17
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1132 17
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1133 17
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1134 17
                $conditions = [];
1135
1136 17
                if ($join->conditionalExpression) {
1137 15
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1138
                }
1139
1140 17
                $condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER)
1141 3
                    ? ' AND '
1142 17
                    : ' ON ';
1143
1144 17
                $sql .= $this->walkRangeVariableDeclaration($joinDeclaration);
1145
1146
                // Apply remaining inheritance restrictions
1147 17
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1148
1149 17
                if ($discrSql) {
1150 3
                    $conditions[] = $discrSql;
1151
                }
1152
1153
                // Apply the filters
1154 17
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1155
1156 17
                if ($filterExpr) {
1157
                    $conditions[] = $filterExpr;
1158
                }
1159
1160 17
                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...
1161 15
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1162
                }
1163
1164 17
                break;
1165
1166 227
            case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration):
1167 227
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1168 225
                break;
1169
        }
1170
1171 242
        return $sql;
1172
    }
1173
1174
    /**
1175
     * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1176
     *
1177
     * @param AST\CoalesceExpression $coalesceExpression
1178
     *
1179
     * @return string The SQL.
1180
     */
1181 2
    public function walkCoalesceExpression($coalesceExpression)
1182
    {
1183 2
        $sql = 'COALESCE(';
1184
1185 2
        $scalarExpressions = [];
1186
1187 2
        foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1188 2
            $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1189
        }
1190
1191 2
        $sql .= implode(', ', $scalarExpressions) . ')';
1192
1193 2
        return $sql;
1194
    }
1195
1196
    /**
1197
     * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1198
     *
1199
     * @param AST\NullIfExpression $nullIfExpression
1200
     *
1201
     * @return string The SQL.
1202
     */
1203 3
    public function walkNullIfExpression($nullIfExpression)
1204
    {
1205 3
        $firstExpression = is_string($nullIfExpression->firstExpression)
1206
            ? $this->conn->quote($nullIfExpression->firstExpression)
1207 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1208
1209 3
        $secondExpression = is_string($nullIfExpression->secondExpression)
1210
            ? $this->conn->quote($nullIfExpression->secondExpression)
1211 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1212
1213 3
        return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1214
    }
1215
1216
    /**
1217
     * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1218
     *
1219
     * @param AST\GeneralCaseExpression $generalCaseExpression
1220
     *
1221
     * @return string The SQL.
1222
     */
1223 9 View Code Duplication
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1224
    {
1225 9
        $sql = 'CASE';
1226
1227 9
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1228 9
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1229 9
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1230
        }
1231
1232 9
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1233
1234 9
        return $sql;
1235
    }
1236
1237
    /**
1238
     * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1239
     *
1240
     * @param AST\SimpleCaseExpression $simpleCaseExpression
1241
     *
1242
     * @return string The SQL.
1243
     */
1244 5 View Code Duplication
    public function walkSimpleCaseExpression($simpleCaseExpression)
1245
    {
1246 5
        $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1247
1248 5
        foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1249 5
            $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1250 5
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1251
        }
1252
1253 5
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1254
1255 5
        return $sql;
1256
    }
1257
1258
    /**
1259
     * {@inheritdoc}
1260
     */
1261 624
    public function walkSelectExpression($selectExpression)
1262
    {
1263 624
        $sql    = '';
1264 624
        $expr   = $selectExpression->expression;
1265 624
        $hidden = $selectExpression->hiddenAliasResultVariable;
1266
1267
        switch (true) {
1268 624
            case ($expr instanceof AST\PathExpression):
1269 101
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1270
                    throw QueryException::invalidPathExpression($expr);
1271
                }
1272
1273 101
                $fieldName = $expr->field;
1274 101
                $dqlAlias  = $expr->identificationVariable;
1275 101
                $qComp     = $this->queryComponents[$dqlAlias];
1276 101
                $class     = $qComp['metadata'];
1277
1278 101
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1279 101
                $tableName   = ($class->isInheritanceTypeJoined())
1280 11
                    ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1281 101
                    : $class->getTableName();
1282
1283 101
                $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1284 101
                $fieldMapping  = $class->fieldMappings[$fieldName];
1285 101
                $columnName    = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1286 101
                $columnAlias   = $this->getSQLColumnAlias($fieldMapping['columnName']);
1287 101
                $col           = $sqlTableAlias . '.' . $columnName;
1288
1289 101
                if (isset($fieldMapping['requireSQLConversion'])) {
1290 2
                    $type = Type::getType($fieldMapping['type']);
1291 2
                    $col  = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1292
                }
1293
1294 101
                $sql .= $col . ' AS ' . $columnAlias;
1295
1296 101
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1297
1298 101
                if ( ! $hidden) {
1299 101
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
1300 101
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1301
                }
1302
1303 101
                break;
1304
1305 574
            case ($expr instanceof AST\AggregateExpression):
1306 565
            case ($expr instanceof AST\Functions\FunctionNode):
1307 525
            case ($expr instanceof AST\SimpleArithmeticExpression):
1308 525
            case ($expr instanceof AST\ArithmeticTerm):
1309 523
            case ($expr instanceof AST\ArithmeticFactor):
1310 522
            case ($expr instanceof AST\ParenthesisExpression):
1311 521
            case ($expr instanceof AST\Literal):
1312 520
            case ($expr instanceof AST\NullIfExpression):
1313 519
            case ($expr instanceof AST\CoalesceExpression):
1314 518
            case ($expr instanceof AST\GeneralCaseExpression):
1315 514 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...
1316 107
                $columnAlias = $this->getSQLColumnAlias('sclr');
1317 107
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1318
1319 107
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1320
1321 107
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1322
1323 107
                if ( ! $hidden) {
1324
                    // We cannot resolve field type here; assume 'string'.
1325 107
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1326
                }
1327 107
                break;
1328
1329 513 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...
1330 15
                $columnAlias = $this->getSQLColumnAlias('sclr');
1331 15
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1332
1333 15
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1334
1335 15
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1336
1337 15
                if ( ! $hidden) {
1338
                    // We cannot resolve field type here; assume 'string'.
1339 13
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1340
                }
1341 15
                break;
1342
1343 509
            case ($expr instanceof AST\NewObjectExpression):
1344 22
                $sql .= $this->walkNewObject($expr,$selectExpression->fieldIdentificationVariable);
1345 22
                break;
1346
1347
            default:
1348
                // IdentificationVariable or PartialObjectExpression
1349 487
                if ($expr instanceof AST\PartialObjectExpression) {
1350 16
                    $dqlAlias = $expr->identificationVariable;
1351 16
                    $partialFieldSet = $expr->partialFieldSet;
1352
                } else {
1353 482
                    $dqlAlias = $expr;
1354 482
                    $partialFieldSet = [];
1355
                }
1356
1357 487
                $queryComp   = $this->queryComponents[$dqlAlias];
1358 487
                $class       = $queryComp['metadata'];
1359 487
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1360
1361 487
                if ( ! isset($this->selectedClasses[$dqlAlias])) {
1362 487
                    $this->selectedClasses[$dqlAlias] = [
1363 487
                        'class'       => $class,
1364 487
                        'dqlAlias'    => $dqlAlias,
1365 487
                        'resultAlias' => $resultAlias
1366
                    ];
1367
                }
1368
1369 487
                $sqlParts = [];
1370
1371
                // Select all fields from the queried class
1372 487 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...
1373 486
                    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...
1374 14
                        continue;
1375
                    }
1376
1377 485
                    $tableName = (isset($mapping['inherited']))
1378 52
                        ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1379 485
                        : $class->getTableName();
1380
1381 485
                    $sqlTableAlias    = $this->getSQLTableAlias($tableName, $dqlAlias);
1382 485
                    $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1383 485
                    $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1384
1385 485
                    $col = $sqlTableAlias . '.' . $quotedColumnName;
1386
1387 485
                    if (isset($mapping['requireSQLConversion'])) {
1388 5
                        $type = Type::getType($mapping['type']);
1389 5
                        $col = $type->convertToPHPValueSQL($col, $this->platform);
1390
                    }
1391
1392 485
                    $sqlParts[] = $col . ' AS '. $columnAlias;
1393
1394 485
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1395
1396 485
                    $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
1397
                }
1398
1399
                // Add any additional fields of subclasses (excluding inherited fields)
1400
                // 1) on Single Table Inheritance: always, since its marginal overhead
1401
                // 2) on Class Table Inheritance only if partial objects are disallowed,
1402
                //    since it requires outer joining subtables.
1403 487
                if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1404 394 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...
1405 42
                        $subClass      = $this->em->getClassMetadata($subClassName);
1406 42
                        $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
1407
1408 42
                        foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1409 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...
1410 42
                                continue;
1411
                            }
1412
1413 34
                            $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1414 34
                            $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
1415
1416 34
                            $col = $sqlTableAlias . '.' . $quotedColumnName;
1417
1418 34
                            if (isset($mapping['requireSQLConversion'])) {
1419
                                $type = Type::getType($mapping['type']);
1420
                                $col = $type->convertToPHPValueSQL($col, $this->platform);
1421
                            }
1422
1423 34
                            $sqlParts[] = $col . ' AS ' . $columnAlias;
1424
1425 34
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1426
1427 42
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1428
                        }
1429
                    }
1430
                }
1431
1432 487
                $sql .= implode(', ', $sqlParts);
1433
        }
1434
1435 624
        return $sql;
1436
    }
1437
1438
    /**
1439
     * {@inheritdoc}
1440
     */
1441
    public function walkQuantifiedExpression($qExpr)
1442
    {
1443
        return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1444
    }
1445
1446
    /**
1447
     * {@inheritdoc}
1448
     */
1449 33
    public function walkSubselect($subselect)
1450
    {
1451 33
        $useAliasesBefore  = $this->useSqlTableAliases;
1452 33
        $rootAliasesBefore = $this->rootAliases;
1453
1454 33
        $this->rootAliases = []; // reset the rootAliases for the subselect
1455 33
        $this->useSqlTableAliases = true;
1456
1457 33
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1458 33
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1459 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...
1460
1461 33
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1462 33
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1463 33
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1464
1465 33
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1466 33
        $this->useSqlTableAliases = $useAliasesBefore;
1467
1468 33
        return $sql;
1469
    }
1470
1471
    /**
1472
     * {@inheritdoc}
1473
     */
1474 33 View Code Duplication
    public function walkSubselectFromClause($subselectFromClause)
1475
    {
1476 33
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1477 33
        $sqlParts               = [];
1478
1479 33
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1480 33
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1481
        }
1482
1483 33
        return ' FROM ' . implode(', ', $sqlParts);
1484
    }
1485
1486
    /**
1487
     * {@inheritdoc}
1488
     */
1489 33
    public function walkSimpleSelectClause($simpleSelectClause)
1490
    {
1491 33
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1492 33
            . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1493
    }
1494
1495
    /**
1496
     * @param \Doctrine\ORM\Query\AST\ParenthesisExpression $parenthesisExpression
1497
     *
1498
     * @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...
1499
     */
1500 22
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1501
    {
1502 22
        return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
1503
    }
1504
1505
    /**
1506
     * @param AST\NewObjectExpression   $newObjectExpression
1507
     * @param null|string               $newObjectResultAlias
1508
     * @return string The SQL.
1509
     */
1510 22
    public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
1511
    {
1512 22
        $sqlSelectExpressions = [];
1513 22
        $objIndex             = $newObjectResultAlias?:$this->newObjectCounter++;
1514
1515 22
        foreach ($newObjectExpression->args as $argIndex => $e) {
1516 22
            $resultAlias = $this->scalarResultCounter++;
1517 22
            $columnAlias = $this->getSQLColumnAlias('sclr');
1518 22
            $fieldType   = 'string';
1519
1520
            switch (true) {
1521 22
                case ($e instanceof AST\NewObjectExpression):
1522
                    $sqlSelectExpressions[] = $e->dispatch($this);
1523
                    break;
1524
1525 22
                case ($e instanceof AST\Subselect):
1526 1
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1527 1
                    break;
1528
1529 22
                case ($e instanceof AST\PathExpression):
1530 22
                    $dqlAlias  = $e->identificationVariable;
1531 22
                    $qComp     = $this->queryComponents[$dqlAlias];
1532 22
                    $class     = $qComp['metadata'];
1533 22
                    $fieldType = $class->fieldMappings[$e->field]['type'];
1534
1535 22
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1536 22
                    break;
1537
1538 6
                case ($e instanceof AST\Literal):
1539 1
                    switch ($e->type) {
1540 1
                        case AST\Literal::BOOLEAN:
1541 1
                            $fieldType = 'boolean';
1542 1
                            break;
1543
1544 1
                        case AST\Literal::NUMERIC:
1545 1
                            $fieldType = is_float($e->value) ? 'float' : 'integer';
1546 1
                            break;
1547
                    }
1548
1549 1
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1550 1
                    break;
1551
1552
                default:
1553 5
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1554 5
                    break;
1555
            }
1556
1557 22
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1558 22
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1559
1560 22
            $this->rsm->newObjectMappings[$columnAlias] = [
1561 22
                'className' => $newObjectExpression->className,
1562 22
                'objIndex'  => $objIndex,
1563 22
                'argIndex'  => $argIndex
1564
            ];
1565
        }
1566
1567 22
        return implode(', ', $sqlSelectExpressions);
1568
    }
1569
1570
    /**
1571
     * {@inheritdoc}
1572
     */
1573 33
    public function walkSimpleSelectExpression($simpleSelectExpression)
1574
    {
1575 33
        $expr = $simpleSelectExpression->expression;
1576 33
        $sql  = ' ';
1577
1578
        switch (true) {
1579 33
            case ($expr instanceof AST\PathExpression):
1580 9
                $sql .= $this->walkPathExpression($expr);
1581 9
                break;
1582
1583 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...
1584
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1585
1586
                $columnAlias = 'sclr' . $this->aliasCounter++;
1587
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1588
1589
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1590
                break;
1591
1592 24
            case ($expr instanceof AST\Functions\FunctionNode):
1593 11
            case ($expr instanceof AST\SimpleArithmeticExpression):
1594 10
            case ($expr instanceof AST\ArithmeticTerm):
1595 9
            case ($expr instanceof AST\ArithmeticFactor):
1596 9
            case ($expr instanceof AST\Literal):
1597 7
            case ($expr instanceof AST\NullIfExpression):
1598 7
            case ($expr instanceof AST\CoalesceExpression):
1599 7
            case ($expr instanceof AST\GeneralCaseExpression):
1600 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...
1601 21
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1602
1603 21
                $columnAlias = $this->getSQLColumnAlias('sclr');
1604 21
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1605
1606 21
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1607 21
                break;
1608
1609 3
            case ($expr instanceof AST\ParenthesisExpression):
1610 1
                $sql .= $this->walkParenthesisExpression($expr);
1611 1
                break;
1612
1613
            default: // IdentificationVariable
1614 2
                $sql .= $this->walkEntityIdentificationVariable($expr);
1615 2
                break;
1616
        }
1617
1618 33
        return $sql;
1619
    }
1620
1621
    /**
1622
     * {@inheritdoc}
1623
     */
1624 76
    public function walkAggregateExpression($aggExpression)
1625
    {
1626 76
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1627 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...
1628
    }
1629
1630
    /**
1631
     * {@inheritdoc}
1632
     */
1633 23
    public function walkGroupByClause($groupByClause)
1634
    {
1635 23
        $sqlParts = [];
1636
1637 23
        foreach ($groupByClause->groupByItems as $groupByItem) {
1638 23
            $sqlParts[] = $this->walkGroupByItem($groupByItem);
1639
        }
1640
1641 23
        return ' GROUP BY ' . implode(', ', $sqlParts);
1642
    }
1643
1644
    /**
1645
     * {@inheritdoc}
1646
     */
1647 23
    public function walkGroupByItem($groupByItem)
1648
    {
1649
        // StateFieldPathExpression
1650 23
        if ( ! is_string($groupByItem)) {
1651 10
            return $this->walkPathExpression($groupByItem);
1652
        }
1653
1654
        // ResultVariable
1655 13
        if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1656 2
            $resultVariable = $this->queryComponents[$groupByItem]['resultVariable'];
1657
1658 2
            if ($resultVariable instanceof AST\PathExpression) {
1659 1
                return $this->walkPathExpression($resultVariable);
1660
            }
1661
1662 1
            if (isset($resultVariable->pathExpression)) {
1663
                return $this->walkPathExpression($resultVariable->pathExpression);
1664
            }
1665
1666 1
            return $this->walkResultVariable($groupByItem);
1667
        }
1668
1669
        // IdentificationVariable
1670 11
        $sqlParts = [];
1671
1672 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->fieldNames as $field) {
1673 11
            $item       = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
1674 11
            $item->type = AST\PathExpression::TYPE_STATE_FIELD;
1675
1676 11
            $sqlParts[] = $this->walkPathExpression($item);
1677
        }
1678
1679 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->associationMappings as $mapping) {
1680 11
            if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
1681 7
                $item       = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
1682 7
                $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1683
1684 7
                $sqlParts[] = $this->walkPathExpression($item);
1685
            }
1686
        }
1687
1688 11
        return implode(', ', $sqlParts);
1689
    }
1690
1691
    /**
1692
     * {@inheritdoc}
1693
     */
1694 36
    public function walkDeleteClause(AST\DeleteClause $deleteClause)
1695
    {
1696 36
        $class     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1697 36
        $tableName = $class->getTableName();
1698 36
        $sql       = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform);
1699
1700 36
        $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1701 36
        $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1702
1703 36
        return $sql;
1704
    }
1705
1706
    /**
1707
     * {@inheritdoc}
1708
     */
1709 25
    public function walkUpdateClause($updateClause)
1710
    {
1711 25
        $class     = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1712 25
        $tableName = $class->getTableName();
1713 25
        $sql       = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform);
1714
1715 25
        $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1716 25
        $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1717
1718 25
        $sql .= ' SET ' . implode(', ', array_map([$this, 'walkUpdateItem'], $updateClause->updateItems));
1719
1720 25
        return $sql;
1721
    }
1722
1723
    /**
1724
     * {@inheritdoc}
1725
     */
1726 29
    public function walkUpdateItem($updateItem)
1727
    {
1728 29
        $useTableAliasesBefore = $this->useSqlTableAliases;
1729 29
        $this->useSqlTableAliases = false;
1730
1731 29
        $sql      = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1732 29
        $newValue = $updateItem->newValue;
1733
1734
        switch (true) {
1735 29
            case ($newValue instanceof AST\Node):
1736 28
                $sql .= $newValue->dispatch($this);
1737 28
                break;
1738
1739 1
            case ($newValue === null):
1740 1
                $sql .= 'NULL';
1741 1
                break;
1742
1743
            default:
1744
                $sql .= $this->conn->quote($newValue);
1745
                break;
1746
        }
1747
1748 29
        $this->useSqlTableAliases = $useTableAliasesBefore;
1749
1750 29
        return $sql;
1751
    }
1752
1753
    /**
1754
     * {@inheritdoc}
1755
     */
1756 681
    public function walkWhereClause($whereClause)
1757
    {
1758 681
        $condSql  = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1759 678
        $discrSql = $this->_generateDiscriminatorColumnConditionSQL($this->rootAliases);
1760
1761 678
        if ($this->em->hasFilters()) {
1762 43
            $filterClauses = [];
1763 43
            foreach ($this->rootAliases as $dqlAlias) {
1764 43
                $class = $this->queryComponents[$dqlAlias]['metadata'];
1765 43
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1766
1767 43
                if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) {
1768 6
                    $filterClauses[] = $filterExpr;
1769
                }
1770
            }
1771
1772 43
            if (count($filterClauses)) {
1773 6
                if ($condSql) {
1774 2
                    $condSql = '(' . $condSql . ') AND ';
1775
                }
1776
1777 6
                $condSql .= implode(' AND ', $filterClauses);
1778
            }
1779
        }
1780
1781 678
        if ($condSql) {
1782 333
            return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1783
        }
1784
1785 421
        if ($discrSql) {
1786 23
            return ' WHERE ' . $discrSql;
1787
        }
1788
1789 401
        return '';
1790
    }
1791
1792
    /**
1793
     * {@inheritdoc}
1794
     */
1795 364
    public function walkConditionalExpression($condExpr)
1796
    {
1797
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1798
        // if only one ConditionalTerm is defined
1799 364
        if ( ! ($condExpr instanceof AST\ConditionalExpression)) {
1800 309
            return $this->walkConditionalTerm($condExpr);
1801
        }
1802
1803 72
        return implode(' OR ', array_map([$this, 'walkConditionalTerm'], $condExpr->conditionalTerms));
1804
    }
1805
1806
    /**
1807
     * {@inheritdoc}
1808
     */
1809 364
    public function walkConditionalTerm($condTerm)
1810
    {
1811
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1812
        // if only one ConditionalFactor is defined
1813 364
        if ( ! ($condTerm instanceof AST\ConditionalTerm)) {
1814 297
            return $this->walkConditionalFactor($condTerm);
1815
        }
1816
1817 90
        return implode(' AND ', array_map([$this, 'walkConditionalFactor'], $condTerm->conditionalFactors));
1818
    }
1819
1820
    /**
1821
     * {@inheritdoc}
1822
     */
1823 364
    public function walkConditionalFactor($factor)
1824
    {
1825
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1826
        // if only one ConditionalPrimary is defined
1827 364
        return ( ! ($factor instanceof AST\ConditionalFactor))
1828 361
            ? $this->walkConditionalPrimary($factor)
1829 361
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1830
    }
1831
1832
    /**
1833
     * {@inheritdoc}
1834
     */
1835 364
    public function walkConditionalPrimary($primary)
1836
    {
1837 364
        if ($primary->isSimpleConditionalExpression()) {
1838 364
            return $primary->simpleConditionalExpression->dispatch($this);
1839
        }
1840
1841 25
        if ($primary->isConditionalExpression()) {
1842 25
            $condExpr = $primary->conditionalExpression;
1843
1844 25
            return '(' . $this->walkConditionalExpression($condExpr) . ')';
0 ignored issues
show
Bug introduced by
It seems like $condExpr defined by $primary->conditionalExpression on line 1842 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...
1845
        }
1846
    }
1847
1848
    /**
1849
     * {@inheritdoc}
1850
     */
1851 5
    public function walkExistsExpression($existsExpr)
1852
    {
1853 5
        $sql = ($existsExpr->not) ? 'NOT ' : '';
1854
1855 5
        $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1856
1857 5
        return $sql;
1858
    }
1859
1860
    /**
1861
     * {@inheritdoc}
1862
     */
1863 6
    public function walkCollectionMemberExpression($collMemberExpr)
1864
    {
1865 6
        $sql = $collMemberExpr->not ? 'NOT ' : '';
1866 6
        $sql .= 'EXISTS (SELECT 1 FROM ';
1867
1868 6
        $entityExpr   = $collMemberExpr->entityExpression;
1869 6
        $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1870
1871 6
        $fieldName = $collPathExpr->field;
1872 6
        $dqlAlias  = $collPathExpr->identificationVariable;
1873
1874 6
        $class = $this->queryComponents[$dqlAlias]['metadata'];
1875
1876
        switch (true) {
1877
            // InputParameter
1878 6
            case ($entityExpr instanceof AST\InputParameter):
1879 4
                $dqlParamKey = $entityExpr->name;
1880 4
                $entitySql   = '?';
1881 4
                break;
1882
1883
            // SingleValuedAssociationPathExpression | IdentificationVariable
1884 2
            case ($entityExpr instanceof AST\PathExpression):
1885 2
                $entitySql = $this->walkPathExpression($entityExpr);
1886 2
                break;
1887
1888
            default:
1889
                throw new \BadMethodCallException("Not implemented");
1890
        }
1891
1892 6
        $assoc = $class->associationMappings[$fieldName];
1893
1894 6
        if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
1895 1
            $targetClass      = $this->em->getClassMetadata($assoc['targetEntity']);
1896 1
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1897 1
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1898
1899 1
            $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE ';
1900
1901 1
            $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
1902 1
            $sqlParts    = [];
1903
1904 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...
1905 1
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform);
1906
1907 1
                $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
1908
            }
1909
1910 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...
1911 1
                if (isset($dqlParamKey)) {
1912 1
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1913
                }
1914
1915 1
                $sqlParts[] = $targetTableAlias . '.'  . $targetColumnName . ' = ' . $entitySql;
1916
            }
1917
1918 1
            $sql .= implode(' AND ', $sqlParts);
1919
        } else { // many-to-many
1920 5
            $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
1921
1922 5
            $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
1923 5
            $joinTable = $owningAssoc['joinTable'];
1924
1925
            // SQL table aliases
1926 5
            $joinTableAlias   = $this->getSQLTableAlias($joinTable['name']);
1927 5
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1928 5
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1929
1930
            // join to target table
1931 5
            $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias
1932 5
                . ' INNER JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' ON ';
1933
1934
            // join conditions
1935 5
            $joinColumns  = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns'];
1936 5
            $joinSqlParts = [];
1937
1938 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...
1939 5
                $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform);
1940
1941 5
                $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn;
1942
            }
1943
1944 5
            $sql .= implode(' AND ', $joinSqlParts);
1945 5
            $sql .= ' WHERE ';
1946
1947 5
            $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
1948 5
            $sqlParts    = [];
1949
1950 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...
1951 5
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform);
1952
1953 5
                $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn;
1954
            }
1955
1956 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...
1957 5
                if (isset($dqlParamKey)) {
1958 3
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1959
                }
1960
1961 5
                $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' IN (' . $entitySql . ')';
1962
            }
1963
1964 5
            $sql .= implode(' AND ', $sqlParts);
1965
        }
1966
1967 6
        return $sql . ')';
1968
    }
1969
1970
    /**
1971
     * {@inheritdoc}
1972
     */
1973 3
    public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
1974
    {
1975 3
        $sizeFunc = new AST\Functions\SizeFunction('size');
1976 3
        $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
1977
1978 3
        return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
1979
    }
1980
1981
    /**
1982
     * {@inheritdoc}
1983
     */
1984 11
    public function walkNullComparisonExpression($nullCompExpr)
1985
    {
1986 11
        $expression = $nullCompExpr->expression;
1987 11
        $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
1988
1989
        // Handle ResultVariable
1990 11
        if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
1991 2
            return $this->walkResultVariable($expression) . $comparison;
1992
        }
1993
1994
        // Handle InputParameter mapping inclusion to ParserResult
1995 9
        if ($expression instanceof AST\InputParameter) {
1996
            return $this->walkInputParameter($expression) . $comparison;
1997
        }
1998
1999 9
        return $expression->dispatch($this) . $comparison;
2000
    }
2001
2002
    /**
2003
     * {@inheritdoc}
2004
     */
2005 85
    public function walkInExpression($inExpr)
2006
    {
2007 85
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2008
2009 84
        $sql .= ($inExpr->subselect)
2010 7
            ? $this->walkSubselect($inExpr->subselect)
2011 84
            : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals));
2012
2013 84
        $sql .= ')';
2014
2015 84
        return $sql;
2016
    }
2017
2018
    /**
2019
     * {@inheritdoc}
2020
     */
2021 9
    public function walkInstanceOfExpression($instanceOfExpr)
2022
    {
2023 9
        $sql = '';
2024
2025 9
        $dqlAlias = $instanceOfExpr->identificationVariable;
2026 9
        $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata'];
2027
2028 9
        if ($class->discriminatorColumn) {
2029 9
            $discrClass = $this->em->getClassMetadata($class->rootEntityName);
2030
        }
2031
2032 9
        if ($this->useSqlTableAliases) {
2033 9
            $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
2034
        }
2035
2036 9
        $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
2037
2038 9
        $sqlParameterList = [];
2039
2040 9
        foreach ($instanceOfExpr->value as $parameter) {
2041 9
            if ($parameter instanceof AST\InputParameter) {
2042 3
                $this->rsm->addMetadataParameterMapping($parameter->name, 'discriminatorValue');
2043
2044 3
                $sqlParameterList[] = $this->walkInputParameter($parameter);
2045
2046 3
                continue;
2047
            }
2048
2049
            // Get name from ClassMetadata to resolve aliases.
2050 6
            $entityClassName    = $this->em->getClassMetadata($parameter)->name;
2051 6
            $discriminatorValue = $class->discriminatorValue;
2052
2053 6
            if ($entityClassName !== $class->name) {
2054 5
                $discrMap = array_flip($class->discriminatorMap);
2055
2056 5
                if ( ! isset($discrMap[$entityClassName])) {
2057 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
2058
                }
2059
2060 4
                $discriminatorValue = $discrMap[$entityClassName];
2061
            }
2062
2063 5
            $sqlParameterList[] = $this->conn->quote($discriminatorValue);
2064
        }
2065
2066 8
        $sql .= '(' . implode(', ', $sqlParameterList) . ')';
2067
2068 8
        return $sql;
2069
    }
2070
2071
    /**
2072
     * {@inheritdoc}
2073
     */
2074 77
    public function walkInParameter($inParam)
2075
    {
2076 77
        return $inParam instanceof AST\InputParameter
2077 68
            ? $this->walkInputParameter($inParam)
2078 77
            : $this->walkLiteral($inParam);
2079
    }
2080
2081
    /**
2082
     * {@inheritdoc}
2083
     */
2084 148
    public function walkLiteral($literal)
2085
    {
2086 148
        switch ($literal->type) {
2087 148
            case AST\Literal::STRING:
2088 50
                return $this->conn->quote($literal->value);
2089
2090 111
            case AST\Literal::BOOLEAN:
2091 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...
2092
2093 104
            case AST\Literal::NUMERIC:
2094 104
                return $literal->value;
2095
2096
            default:
2097
                throw QueryException::invalidLiteral($literal);
2098
        }
2099
    }
2100
2101
    /**
2102
     * {@inheritdoc}
2103
     */
2104 6
    public function walkBetweenExpression($betweenExpr)
2105
    {
2106 6
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2107
2108 6
        if ($betweenExpr->not) {
2109 2
            $sql .= ' NOT';
2110
        }
2111
2112 6
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2113 6
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2114
2115 6
        return $sql;
2116
    }
2117
2118
    /**
2119
     * {@inheritdoc}
2120
     */
2121 9
    public function walkLikeExpression($likeExpr)
2122
    {
2123 9
        $stringExpr = $likeExpr->stringExpression;
2124 9
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2125 1
            ? $this->walkResultVariable($stringExpr)
2126 9
            : $stringExpr->dispatch($this);
2127
2128 9
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2129
2130 9
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2131 4
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2132 6
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2133 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2134 6
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2135 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2136
        } else {
2137 6
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2138
        }
2139
2140 9
        if ($likeExpr->escapeChar) {
2141 1
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2142
        }
2143
2144 9
        return $sql;
2145
    }
2146
2147
    /**
2148
     * {@inheritdoc}
2149
     */
2150 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2151
    {
2152 5
        return $this->walkPathExpression($stateFieldPathExpression);
2153
    }
2154
2155
    /**
2156
     * {@inheritdoc}
2157
     */
2158 261
    public function walkComparisonExpression($compExpr)
2159
    {
2160 261
        $leftExpr  = $compExpr->leftExpression;
2161 261
        $rightExpr = $compExpr->rightExpression;
2162 261
        $sql       = '';
2163
2164 261
        $sql .= ($leftExpr instanceof AST\Node)
2165 261
            ? $leftExpr->dispatch($this)
2166 260
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2167
2168 260
        $sql .= ' ' . $compExpr->operator . ' ';
2169
2170 260
        $sql .= ($rightExpr instanceof AST\Node)
2171 258
            ? $rightExpr->dispatch($this)
2172 260
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2173
2174 260
        return $sql;
2175
    }
2176
2177
    /**
2178
     * {@inheritdoc}
2179
     */
2180 216
    public function walkInputParameter($inputParam)
2181
    {
2182 216
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2183
2184 216
        $parameter = $this->query->getParameter($inputParam->name);
2185
2186 216
        if ($parameter && Type::hasType($type = $parameter->getType())) {
2187 55
            return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2188
        }
2189
2190 167
        return '?';
2191
    }
2192
2193
    /**
2194
     * {@inheritdoc}
2195
     */
2196 327
    public function walkArithmeticExpression($arithmeticExpr)
2197
    {
2198 327
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2199 327
            ? $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...
2200 325
            : '(' . $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...
2201
    }
2202
2203
    /**
2204
     * {@inheritdoc}
2205
     */
2206 390
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2207
    {
2208 390
        if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2209 340
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2210
        }
2211
2212 75
        return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms));
2213
    }
2214
2215
    /**
2216
     * {@inheritdoc}
2217
     */
2218 411
    public function walkArithmeticTerm($term)
2219
    {
2220 411 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...
2221 21
            return (isset($this->queryComponents[$term]))
2222 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2223 21
                : $term;
2224
        }
2225
2226
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2227
        // if only one ArithmeticFactor is defined
2228 410
        if ( ! ($term instanceof AST\ArithmeticTerm)) {
2229 388
            return $this->walkArithmeticFactor($term);
2230
        }
2231
2232 47
        return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors));
2233
    }
2234
2235
    /**
2236
     * {@inheritdoc}
2237
     */
2238 411
    public function walkArithmeticFactor($factor)
2239
    {
2240 411 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...
2241 47
            return (isset($this->queryComponents[$factor]))
2242 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2243 47
                : $factor;
2244
        }
2245
2246
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2247
        // if only one ArithmeticPrimary is defined
2248 411
        if ( ! ($factor instanceof AST\ArithmeticFactor)) {
2249 410
            return $this->walkArithmeticPrimary($factor);
2250
        }
2251
2252 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2253
2254 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2255
    }
2256
2257
    /**
2258
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2259
     *
2260
     * @param mixed $primary
2261
     *
2262
     * @return string The SQL.
2263
     */
2264 411
    public function walkArithmeticPrimary($primary)
2265
    {
2266 411
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2267
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2268
        }
2269
2270 411
        if ($primary instanceof AST\Node) {
2271 411
            return $primary->dispatch($this);
2272
        }
2273
2274
        return $this->walkEntityIdentificationVariable($primary);
2275
    }
2276
2277
    /**
2278
     * {@inheritdoc}
2279
     */
2280 18
    public function walkStringPrimary($stringPrimary)
2281
    {
2282 18
        return (is_string($stringPrimary))
2283
            ? $this->conn->quote($stringPrimary)
2284 18
            : $stringPrimary->dispatch($this);
2285
    }
2286
2287
    /**
2288
     * {@inheritdoc}
2289
     */
2290 30
    public function walkResultVariable($resultVariable)
2291
    {
2292 30
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2293
2294 30
        if (is_array($resultAlias)) {
2295 1
            return implode(', ', $resultAlias);
2296
        }
2297
2298 29
        return $resultAlias;
2299
    }
2300
}
2301