Failed Conditions
Pull Request — master (#6392)
by Alessandro
11:37
created

getChildDiscriminatorsFromClassMetadata()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6

Importance

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