Failed Conditions
Pull Request — master (#6546)
by Jáchym
11:13
created

_generateOrderedCollectionOrderByItems()   C

Complexity

Conditions 7
Paths 3

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 7.2269

Importance

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