Completed
Pull Request — master (#6500)
by Mathew
17:08
created

SqlWalker::setSQLTableAlias()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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

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

Loading history...
197
     */
198
    public function getQuery()
199
    {
200
        return $this->query;
201
    }
202
203
    /**
204
     * Gets the Connection used by the walker.
205
     *
206
     * @return \Doctrine\DBAL\Connection
207
     */
208 34
    public function getConnection()
209
    {
210 34
        return $this->conn;
211
    }
212
213
    /**
214
     * Gets the EntityManager used by the walker.
215
     *
216
     * @return \Doctrine\ORM\EntityManager
217
     */
218 20
    public function getEntityManager()
219
    {
220 20
        return $this->em;
221
    }
222
223
    /**
224
     * Gets the information about a single query component.
225
     *
226
     * @param string $dqlAlias The DQL alias.
227
     *
228
     * @return array
229
     */
230 17
    public function getQueryComponent($dqlAlias)
231
    {
232 17
        return $this->queryComponents[$dqlAlias];
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function getQueryComponents()
239
    {
240
        return $this->queryComponents;
241
    }
242
243
    /**
244
     * {@inheritdoc}
245
     */
246 1
    public function setQueryComponent($dqlAlias, array $queryComponent)
247
    {
248 1
        $requiredKeys = ['metadata', 'parent', 'relation', 'map', 'nestingLevel', 'token'];
249
250 1
        if (array_diff($requiredKeys, array_keys($queryComponent))) {
251 1
            throw QueryException::invalidQueryComponent($dqlAlias);
252
        }
253
254
        $this->queryComponents[$dqlAlias] = $queryComponent;
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260 620
    public function getExecutor($AST)
261
    {
262
        switch (true) {
263 620
            case ($AST instanceof AST\DeleteStatement):
264 37
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
265
266 37
                return ($primaryClass->isInheritanceTypeJoined())
267 1
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
268 37
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
269
270 586
            case ($AST instanceof AST\UpdateStatement):
271 28
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
272
273 28
                return ($primaryClass->isInheritanceTypeJoined())
274 3
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
275 28
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
276
277
            default:
278 561
                return new Exec\SingleSelectExecutor($AST, $this);
279
        }
280
    }
281
282
    /**
283
     * Generates a unique, short SQL table alias.
284
     *
285
     * @param string $tableName Table name
286
     * @param string $dqlAlias  The DQL alias.
287
     *
288
     * @return string Generated table alias.
289
     */
290 572
    public function getSQLTableAlias($tableName, $dqlAlias = '')
291
    {
292 572
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
293
294 572
        if ( ! isset($this->tableAliasMap[$tableName])) {
295 572
            $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
296 572
                . $this->tableAliasCounter++ . '_';
297
        }
298
299 572
        return $this->tableAliasMap[$tableName];
300
    }
301
302
    /**
303
     * Forces the SqlWalker to use a specific alias for a table name, rather than
304
     * generating an alias on its own.
305
     *
306
     * @param string $tableName
307
     * @param string $alias
308
     * @param string $dqlAlias
309
     *
310
     * @return string
311
     */
312 63
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
313
    {
314 63
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
315
316 63
        $this->tableAliasMap[$tableName] = $alias;
317
318 63
        return $alias;
319
    }
320
321
    /**
322
     * Gets an SQL column alias for a column name.
323
     *
324
     * @param string $columnName
325
     *
326
     * @return string
327
     */
328 561
    public function getSQLColumnAlias($columnName)
329
    {
330 561
        return $this->quoteStrategy->getColumnAlias($columnName, $this->aliasCounter++, $this->platform);
331
    }
332
333
    /**
334
     * Generates the SQL JOINs that are necessary for Class Table Inheritance
335
     * for the given class.
336
     *
337
     * @param ClassMetadata $class    The class for which to generate the joins.
338
     * @param string        $dqlAlias The DQL alias of the class.
339
     *
340
     * @return string The SQL.
341
     */
342 81
    private function _generateClassTableInheritanceJoins($class, $dqlAlias)
343
    {
344 81
        $sql = '';
345
346 81
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
347
348
        // INNER JOIN parent class tables
349 81
        foreach ($class->parentClasses as $parentClassName) {
350 54
            $parentClass = $this->em->getClassMetadata($parentClassName);
351 54
            $tableAlias  = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
352
353
            // If this is a joined association we must use left joins to preserve the correct result.
354 54
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
355 54
            $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
356
357 54
            $sqlParts = [];
358
359 54
            foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
360 54
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
361
            }
362
363
            // Add filters on the root class
364 54
            if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
365 1
                $sqlParts[] = $filterSql;
366
            }
367
368 54
            $sql .= implode(' AND ', $sqlParts);
369
        }
370
371
        // Ignore subclassing inclusion if partial objects is disallowed
372 81
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
373 19
            return $sql;
374
        }
375
376
        // LEFT JOIN child class tables
377 62
        foreach ($class->subClasses as $subClassName) {
378 28
            $subClass   = $this->em->getClassMetadata($subClassName);
379 28
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
380
381 28
            $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
382
383 28
            $sqlParts = [];
384
385 28
            foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
386 28
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
387
            }
388
389 28
            $sql .= implode(' AND ', $sqlParts);
390
        }
391
392 62
        return $sql;
393
    }
394
395
    /**
396
     * @return string
397
     */
398 555
    private function _generateOrderedCollectionOrderByItems()
399
    {
400 555
        $orderedColumns = [];
401
402 555
        foreach ($this->selectedClasses as $selectedClass) {
403 454
            $dqlAlias  = $selectedClass['dqlAlias'];
404 454
            $qComp     = $this->queryComponents[$dqlAlias];
405
406 454
            if ( ! isset($qComp['relation']['orderBy'])) {
407 454
                continue;
408
            }
409
410 6
            $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
411
412 6
            foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
413 6
                $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
414 6
                $tableName  = ($qComp['metadata']->isInheritanceTypeJoined())
415 1
                    ? $persister->getOwningTable($fieldName)
416 6
                    : $qComp['metadata']->getTableName();
417
418 6
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
419
420
                // OrderByClause should replace an ordered relation. see - DDC-2475
421 6
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
422 1
                    continue;
423
                }
424
425 6
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
426 6
                $orderedColumns[] = $orderedColumn . ' ' . $orientation;
427
            }
428
        }
429
430 555
        return implode(', ', $orderedColumns);
431
    }
432
433
    /**
434
     * Generates a discriminator column SQL condition for the class with the given DQL alias.
435
     *
436
     * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
437
     *
438
     * @return string
439
     */
440 615
    private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
441
    {
442 615
        $sqlParts = [];
443
444 615
        foreach ($dqlAliases as $dqlAlias) {
445 615
            $class = $this->queryComponents[$dqlAlias]['metadata'];
446
447 615
            if ( ! $class->isInheritanceTypeSingleTable()) continue;
448
449 38
            $conn   = $this->em->getConnection();
450 38
            $values = [];
451
452 38
            if ($class->discriminatorValue !== null) { // discriminators can be 0
453 20
                $values[] = $conn->quote($class->discriminatorValue);
454
            }
455
456 38
            foreach ($class->subClasses as $subclassName) {
457 28
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
458
            }
459
460 38
            $sqlTableAlias = ($this->useSqlTableAliases)
461 33
                ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
462 38
                : '';
463
464 38
            $sqlParts[] = $sqlTableAlias . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
465
        }
466
467 615
        $sql = implode(' AND ', $sqlParts);
468
469 615
        return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
470
    }
471
472
    /**
473
     * Generates the filter SQL for a given entity and table alias.
474
     *
475
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
476
     * @param string        $targetTableAlias The table alias of the joined/selected table.
477
     *
478
     * @return string The SQL query part to add to a query.
479
     */
480 286
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
481
    {
482 286
        if (!$this->em->hasFilters()) {
483 250
            return '';
484
        }
485
486 41
        switch($targetEntity->inheritanceType) {
487 41
            case ClassMetadata::INHERITANCE_TYPE_NONE:
488 31
                break;
489 10
            case ClassMetadata::INHERITANCE_TYPE_JOINED:
490
                // The classes in the inheritance will be added to the query one by one,
491
                // but only the root node is getting filtered
492 6
                if ($targetEntity->name !== $targetEntity->rootEntityName) {
493 4
                    return '';
494
                }
495 6
                break;
496 4
            case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
497
                // With STI the table will only be queried once, make sure that the filters
498
                // are added to the root entity
499 4
                $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
500 4
                break;
501
            default:
502
                //@todo: throw exception?
503
                return '';
504
        }
505
506 41
        $filterClauses = [];
507 41
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
508 10
            if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
509 10
                $filterClauses[] = '(' . $filterExpr . ')';
510
            }
511
        }
512
513 41
        return implode(' AND ', $filterClauses);
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     */
519 561
    public function walkSelectStatement(AST\SelectStatement $AST)
520
    {
521 561
        $limit    = $this->query->getMaxResults();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Doctrine\ORM\AbstractQuery as the method getMaxResults() does only exist in the following sub-classes of Doctrine\ORM\AbstractQuery: Doctrine\ORM\Query. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
527
528 556
        if ($AST->groupByClause) {
529 7
            $sql .= $this->walkGroupByClause($AST->groupByClause);
530
        }
531
532 556
        if ($AST->havingClause) {
533 3
            $sql .= $this->walkHavingClause($AST->havingClause);
534
        }
535
536 556
        if ($AST->orderByClause) {
537 129
            $sql .= $this->walkOrderByClause($AST->orderByClause);
538
        }
539
540 555
        if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) {
541 6
            $sql .= ' ORDER BY ' . $orderBySql;
542
        }
543
544 555
        if ($limit !== null || $offset !== null) {
545 39
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
546
        }
547
548 555
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
549 550
            return $sql;
550
        }
551
552 5
        if ($lockMode === LockMode::PESSIMISTIC_READ) {
553 3
            return $sql . ' ' . $this->platform->getReadLockSQL();
554
        }
555
556 2
        if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
557 1
            return $sql . ' ' . $this->platform->getWriteLockSQL();
558
        }
559
560 1
        if ($lockMode !== LockMode::OPTIMISTIC) {
561
            throw QueryException::invalidLockMode();
562
        }
563
564 1
        foreach ($this->selectedClasses as $selectedClass) {
565 1
            if ( ! $selectedClass['class']->isVersioned) {
566 1
                throw OptimisticLockException::lockFailed($selectedClass['class']->name);
567
            }
568
        }
569
570
        return $sql;
571
    }
572
573
    /**
574
     * {@inheritdoc}
575
     */
576 25
    public function walkUpdateStatement(AST\UpdateStatement $AST)
577
    {
578 25
        $this->useSqlTableAliases = false;
579 25
        $this->rsm->isSelect      = false;
580
581 25
        return $this->walkUpdateClause($AST->updateClause)
582 25
            . $this->walkWhereClause($AST->whereClause);
0 ignored issues
show
Bug introduced by
It seems like $AST->whereClause can be null; however, walkWhereClause() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
595
    }
596
597
    /**
598
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
599
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
600
     *
601
     * @param string $identVariable
602
     *
603
     * @return string
604
     */
605 2
    public function walkEntityIdentificationVariable($identVariable)
606
    {
607 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
608 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
609 2
        $sqlParts   = [];
610
611 2
        foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
612 2
            $sqlParts[] = $tableAlias . '.' . $columnName;
613
        }
614
615 2
        return implode(', ', $sqlParts);
616
    }
617
618
    /**
619
     * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
620
     *
621
     * @param string $identificationVariable
622
     * @param string $fieldName
623
     *
624
     * @return string The SQL.
625
     */
626 360
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
627
    {
628 360
        $class = $this->queryComponents[$identificationVariable]['metadata'];
629
630
        if (
631 360
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
632 360
            isset($class->fieldMappings[$fieldName]['inherited'])
633
        ) {
634 29
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
635
        }
636
637 360
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
638
    }
639
640
    /**
641
     * {@inheritdoc}
642
     */
643 429
    public function walkPathExpression($pathExpr)
644
    {
645 429
        $sql = '';
646
647 429
        switch ($pathExpr->type) {
648 429
            case AST\PathExpression::TYPE_STATE_FIELD:
649 408
                $fieldName = $pathExpr->field;
650 408
                $dqlAlias = $pathExpr->identificationVariable;
651 408
                $class = $this->queryComponents[$dqlAlias]['metadata'];
652
653 408
                if ($this->useSqlTableAliases) {
654 360
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
655
                }
656
657 408
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
658 408
                break;
659
660 53
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
661
                // 1- the owning side:
662
                //    Just use the foreign key, i.e. u.group_id
663 53
                $fieldName = $pathExpr->field;
664 53
                $dqlAlias = $pathExpr->identificationVariable;
665 53
                $class = $this->queryComponents[$dqlAlias]['metadata'];
666
667 53
                if (isset($class->associationMappings[$fieldName]['inherited'])) {
668 2
                    $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
669
                }
670
671 53
                $assoc = $class->associationMappings[$fieldName];
672
673 53
                if ( ! $assoc['isOwningSide']) {
674 2
                    throw QueryException::associationPathInverseSideNotSupported();
675
                }
676
677
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
678 51
                if (count($assoc['sourceToTargetKeyColumns']) > 1) {
679 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
680
                }
681
682 50
                if ($this->useSqlTableAliases) {
683 47
                    $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
684
                }
685
686 50
                $sql .= reset($assoc['targetToSourceKeyColumns']);
687 50
                break;
688
689
            default:
690
                throw QueryException::invalidPathExpression($pathExpr);
691
        }
692
693 426
        return $sql;
694
    }
695
696
    /**
697
     * {@inheritdoc}
698
     */
699 561
    public function walkSelectClause($selectClause)
700
    {
701 561
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
702 561
        $sqlSelectExpressions = array_filter(array_map([$this, 'walkSelectExpression'], $selectClause->selectExpressions));
703
704 561
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
705 1
            $this->query->setHint(self::HINT_DISTINCT, true);
706
        }
707
708 561
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
709 417
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
710
            ||
711 257
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
712 561
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
713
714 561
        foreach ($this->selectedClasses as $selectedClass) {
715 460
            $class       = $selectedClass['class'];
716 460
            $dqlAlias    = $selectedClass['dqlAlias'];
717 460
            $resultAlias = $selectedClass['resultAlias'];
718
719
            // Register as entity or joined entity result
720 460
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
721 460
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
722
            } else {
723 151
                $this->rsm->addJoinedEntityResult(
724 151
                    $class->name,
725
                    $dqlAlias,
726 151
                    $this->queryComponents[$dqlAlias]['parent'],
727 151
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
728
                );
729
            }
730
731 460
            if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
732
                // Add discriminator columns to SQL
733 90
                $rootClass   = $this->em->getClassMetadata($class->rootEntityName);
734 90
                $tblAlias    = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
735 90
                $discrColumn = $rootClass->discriminatorColumn;
736 90
                $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
737
738 90
                $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
739
740 90
                $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
741 90
                $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], false, $discrColumn['type']);
742
            }
743
744
            // Add foreign key columns to SQL, if necessary
745 460
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
746 173
                continue;
747
            }
748
749
            // Add foreign key columns of class and also parent classes
750 332
            foreach ($class->associationMappings as $assoc) {
751 299
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
752 250
                    continue;
753 276
                } else if ( !$addMetaColumns && !isset($assoc['id'])) {
754
                    continue;
755
                }
756
757 276
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
758 276
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
759 276
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
760 276
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
761
762 276
                foreach ($assoc['joinColumns'] as $joinColumn) {
763 276
                    $columnName  = $joinColumn['name'];
764 276
                    $columnAlias = $this->getSQLColumnAlias($columnName);
765 276
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
766
767 276
                    $quotedColumnName       = $this->quoteStrategy->getJoinColumnName($joinColumn, $class, $this->platform);
768 276
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $quotedColumnName . ' AS ' . $columnAlias;
769
770 276
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
771
                }
772
            }
773
774
            // Add foreign key columns to SQL, if necessary
775 332
            if ( ! $addMetaColumns) {
776 8
                continue;
777
            }
778
779
            // Add foreign key columns of subclasses
780 327
            foreach ($class->subClasses as $subClassName) {
781 31
                $subClass      = $this->em->getClassMetadata($subClassName);
782 31
                $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
783
784 31
                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
                        foreach ($assoc['joinColumns'] as $joinColumn) {
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 327
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
800
                        }
801
                    }
802
                }
803
            }
804
        }
805
806 561
        $sql .= implode(', ', $sqlSelectExpressions);
807
808 561
        return $sql;
809
    }
810
811
    /**
812
     * {@inheritdoc}
813
     */
814 563
    public function walkFromClause($fromClause)
815
    {
816 563
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
817 563
        $sqlParts = [];
818
819 563
        foreach ($identificationVarDecls as $identificationVariableDecl) {
820 563
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
821
        }
822
823 561
        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 564
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
834
    {
835 564
        $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 564
        if ($identificationVariableDecl->indexBy) {
838 5
            $this->walkIndexBy($identificationVariableDecl->indexBy);
839
        }
840
841 564
        foreach ($identificationVariableDecl->joins as $join) {
842 224
            $sql .= $this->walkJoin($join);
843
        }
844
845 562
        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 564
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
878
    {
879 564
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
880 564
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
881
882 564
        if ($rangeVariableDeclaration->isRoot) {
883 564
            $this->rootAliases[] = $dqlAlias;
884
        }
885
886 564
        $sql = $this->platform->appendLockHint(
887 564
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
888 564
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
889 564
            $this->query->getHint(Query::HINT_LOCK_MODE)
890
        );
891
892 564
        if ($class->isInheritanceTypeJoined()) {
893 77
            $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
894
        }
895
896 564
        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 208
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
911
    {
912 208
        $sql = '';
913
914 208
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
915 208
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
916 208
        $indexBy                   = $joinAssociationDeclaration->indexBy;
917
918 208
        $relation        = $this->queryComponents[$joinedDqlAlias]['relation'];
919 208
        $targetClass     = $this->em->getClassMetadata($relation['targetEntity']);
920 208
        $sourceClass     = $this->em->getClassMetadata($relation['sourceEntity']);
921 208
        $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
922
923 208
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
924 208
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
925
926
        // Ensure we got the owning side, since it has all mapping info
927 208
        $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
928
929 208
        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 206
        $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 206
            case ($assoc['type'] & ClassMetadata::TO_ONE):
942 171
                $conditions = [];
943
944 171
                foreach ($assoc['joinColumns'] as $joinColumn) {
945 171
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
946 171
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
947
948 171
                    if ($relation['isOwningSide']) {
949 99
                        $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
950
951 99
                        continue;
952
                    }
953
954 103
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
955
                }
956
957
                // Apply remaining inheritance restrictions
958 171
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
959
960 171
                if ($discrSql) {
961 3
                    $conditions[] = $discrSql;
962
                }
963
964
                // Apply the filters
965 171
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
966
967 171
                if ($filterExpr) {
968 1
                    $conditions[] = $filterExpr;
969
                }
970
971
                $targetTableJoin = [
972 171
                    'table' => $targetTableName . ' ' . $targetTableAlias,
973 171
                    'condition' => implode(' AND ', $conditions),
974
                ];
975 171
                break;
976
977 45
            case ($assoc['type'] == ClassMetadata::MANY_TO_MANY):
978
                // Join relation table
979 45
                $joinTable      = $assoc['joinTable'];
980 45
                $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
981 45
                $joinTableName  = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
982
983 45
                $conditions      = [];
984 45
                $relationColumns = ($relation['isOwningSide'])
985 42
                    ? $assoc['joinTable']['joinColumns']
986 45
                    : $assoc['joinTable']['inverseJoinColumns'];
987
988 45
                foreach ($relationColumns as $joinColumn) {
989 45
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
990 45
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
991
992 45
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
993
                }
994
995 45
                $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
996
997
                // Join target table
998 45
                $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
999
1000 45
                $conditions      = [];
1001 45
                $relationColumns = ($relation['isOwningSide'])
1002 42
                    ? $assoc['joinTable']['inverseJoinColumns']
1003 45
                    : $assoc['joinTable']['joinColumns'];
1004
1005 45
                foreach ($relationColumns as $joinColumn) {
1006 45
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1007 45
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1008
1009 45
                    $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1010
                }
1011
1012
                // Apply remaining inheritance restrictions
1013 45
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$joinedDqlAlias]);
1014
1015 45
                if ($discrSql) {
1016
                    $conditions[] = $discrSql;
1017
                }
1018
1019
                // Apply the filters
1020 45
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1021
1022 45
                if ($filterExpr) {
1023 1
                    $conditions[] = $filterExpr;
1024
                }
1025
1026
                $targetTableJoin = [
1027 45
                    'table' => $targetTableName . ' ' . $targetTableAlias,
1028 45
                    'condition' => implode(' AND ', $conditions),
1029
                ];
1030 45
                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 206
        $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1038
1039 206
        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 9
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1046
            }
1047
        } else {
1048 197
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1049
        }
1050
1051 206
        if ($withCondition) {
1052 5
            $sql .= ' AND ' . $withCondition;
1053
        }
1054
1055
        // Apply the indexes
1056 206
        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 201
        } else if (isset($relation['indexBy'])) {
1060 3
            $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
1061
        }
1062
1063 206
        return $sql;
1064
    }
1065
1066
    /**
1067
     * {@inheritdoc}
1068
     */
1069 54
    public function walkFunction($function)
1070
    {
1071 54
        return $function->getSql($this);
1072
    }
1073
1074
    /**
1075
     * {@inheritdoc}
1076
     */
1077 132
    public function walkOrderByClause($orderByClause)
1078
    {
1079 132
        $orderByItems = array_map([$this, 'walkOrderByItem'], $orderByClause->orderByItems);
1080
1081 131
        if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
1082
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1083
        }
1084
1085 131
        return ' ORDER BY ' . implode(', ', $orderByItems);
1086
    }
1087
1088
    /**
1089
     * {@inheritdoc}
1090
     */
1091 148
    public function walkOrderByItem($orderByItem)
1092
    {
1093 148
        $type = strtoupper($orderByItem->type);
1094 148
        $expr = $orderByItem->expression;
1095 148
        $sql  = ($expr instanceof AST\Node)
1096 145
            ? $expr->dispatch($this)
1097 147
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1098
1099 147
        $this->orderedColumnsMap[$sql] = $type;
1100
1101 147
        if ($expr instanceof AST\Subselect) {
1102
            return '(' . $sql . ') ' . $type;
1103
        }
1104
1105 147
        return $sql . ' ' . $type;
1106
    }
1107
1108
    /**
1109
     * {@inheritdoc}
1110
     */
1111 3
    public function walkHavingClause($havingClause)
1112
    {
1113 3
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1114
    }
1115
1116
    /**
1117
     * {@inheritdoc}
1118
     */
1119 224
    public function walkJoin($join)
1120
    {
1121 224
        $joinType        = $join->joinType;
1122 224
        $joinDeclaration = $join->joinAssociationDeclaration;
1123
1124 224
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1125 49
            ? ' LEFT JOIN '
1126 224
            : ' INNER JOIN ';
1127
1128
        switch (true) {
1129 224
            case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration):
1130 16
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1131 16
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1132 16
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1133 16
                $conditions = [];
1134
1135 16
                if ($join->conditionalExpression) {
1136 14
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1137
                }
1138
1139 16
                $condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER)
1140 3
                    ? ' AND '
1141 16
                    : ' ON ';
1142
1143 16
                $sql .= $this->walkRangeVariableDeclaration($joinDeclaration);
1144
1145
                // Apply remaining inheritance restrictions
1146 16
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL([$dqlAlias]);
1147
1148 16
                if ($discrSql) {
1149 3
                    $conditions[] = $discrSql;
1150
                }
1151
1152
                // Apply the filters
1153 16
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1154
1155 16
                if ($filterExpr) {
1156
                    $conditions[] = $filterExpr;
1157
                }
1158
1159 16
                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 14
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1161
                }
1162
1163 16
                break;
1164
1165 208
            case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration):
1166 208
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1167 206
                break;
1168
        }
1169
1170 222
        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 8
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1223
    {
1224 8
        $sql = 'CASE';
1225
1226 8
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1227 8
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1228 8
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1229
        }
1230
1231 8
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1232
1233 8
        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
    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 561
    public function walkSelectExpression($selectExpression)
1261
    {
1262 561
        $sql    = '';
1263 561
        $expr   = $selectExpression->expression;
1264 561
        $hidden = $selectExpression->hiddenAliasResultVariable;
1265
1266
        switch (true) {
1267 561
            case ($expr instanceof AST\PathExpression):
1268 91
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1269
                    throw QueryException::invalidPathExpression($expr);
1270
                }
1271
1272 91
                $fieldName = $expr->field;
1273 91
                $dqlAlias  = $expr->identificationVariable;
1274 91
                $qComp     = $this->queryComponents[$dqlAlias];
1275 91
                $class     = $qComp['metadata'];
1276
1277 91
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1278 91
                $tableName   = ($class->isInheritanceTypeJoined())
1279 8
                    ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1280 91
                    : $class->getTableName();
1281
1282 91
                $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1283 91
                $fieldMapping  = $class->fieldMappings[$fieldName];
1284 91
                $columnName    = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1285 91
                $columnAlias   = $this->getSQLColumnAlias($fieldMapping['columnName']);
1286 91
                $col           = $sqlTableAlias . '.' . $columnName;
1287
1288 91
                if (isset($fieldMapping['requireSQLConversion'])) {
1289 2
                    $type = Type::getType($fieldMapping['type']);
1290 2
                    $col  = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1291
                }
1292
1293 91
                $sql .= $col . ' AS ' . $columnAlias;
1294
1295 91
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1296
1297 91
                if ( ! $hidden) {
1298 91
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
1299 91
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1300
                }
1301
1302 91
                break;
1303
1304 511
            case ($expr instanceof AST\AggregateExpression):
1305 503
            case ($expr instanceof AST\Functions\FunctionNode):
1306 490
            case ($expr instanceof AST\SimpleArithmeticExpression):
1307 490
            case ($expr instanceof AST\ArithmeticTerm):
1308 490
            case ($expr instanceof AST\ArithmeticFactor):
1309 489
            case ($expr instanceof AST\ParenthesisExpression):
1310 488
            case ($expr instanceof AST\Literal):
1311 487
            case ($expr instanceof AST\NullIfExpression):
1312 486
            case ($expr instanceof AST\CoalesceExpression):
1313 485
            case ($expr instanceof AST\GeneralCaseExpression):
1314 481
            case ($expr instanceof AST\SimpleCaseExpression):
1315 58
                $columnAlias = $this->getSQLColumnAlias('sclr');
1316 58
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1317
1318 58
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1319
1320 58
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1321
1322 58
                if ( ! $hidden) {
1323
                    // We cannot resolve field type here; assume 'string'.
1324 58
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1325
                }
1326 58
                break;
1327
1328 480
            case ($expr instanceof AST\Subselect):
1329 4
                $columnAlias = $this->getSQLColumnAlias('sclr');
1330 4
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1331
1332 4
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1333
1334 4
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1335
1336 4
                if ( ! $hidden) {
1337
                    // We cannot resolve field type here; assume 'string'.
1338 4
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1339
                }
1340 4
                break;
1341
1342 480
            case ($expr instanceof AST\NewObjectExpression):
1343 20
                $sql .= $this->walkNewObject($expr,$selectExpression->fieldIdentificationVariable);
1344 20
                break;
1345
1346
            default:
1347
                // IdentificationVariable or PartialObjectExpression
1348 460
                if ($expr instanceof AST\PartialObjectExpression) {
1349 16
                    $dqlAlias = $expr->identificationVariable;
1350 16
                    $partialFieldSet = $expr->partialFieldSet;
1351
                } else {
1352 455
                    $dqlAlias = $expr;
1353 455
                    $partialFieldSet = [];
1354
                }
1355
1356 460
                $queryComp   = $this->queryComponents[$dqlAlias];
1357 460
                $class       = $queryComp['metadata'];
1358 460
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1359
1360 460
                if ( ! isset($this->selectedClasses[$dqlAlias])) {
1361 460
                    $this->selectedClasses[$dqlAlias] = [
1362 460
                        'class'       => $class,
1363 460
                        'dqlAlias'    => $dqlAlias,
1364 460
                        'resultAlias' => $resultAlias
1365
                    ];
1366
                }
1367
1368 460
                $sqlParts = [];
1369
1370
                // Select all fields from the queried class
1371 460
                foreach ($class->fieldMappings as $fieldName => $mapping) {
1372 459
                    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 458
                    $tableName = (isset($mapping['inherited']))
1377 51
                        ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1378 458
                        : $class->getTableName();
1379
1380 458
                    $sqlTableAlias    = $this->getSQLTableAlias($tableName, $dqlAlias);
1381 458
                    $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1382 458
                    $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1383
1384 458
                    $col = $sqlTableAlias . '.' . $quotedColumnName;
1385
1386 458
                    if (isset($mapping['requireSQLConversion'])) {
1387 5
                        $type = Type::getType($mapping['type']);
1388 5
                        $col = $type->convertToPHPValueSQL($col, $this->platform);
1389
                    }
1390
1391 458
                    $sqlParts[] = $col . ' AS '. $columnAlias;
1392
1393 458
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1394
1395 458
                    $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 460
                if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1403 370
                    foreach ($class->subClasses as $subClassName) {
1404 42
                        $subClass      = $this->em->getClassMetadata($subClassName);
1405 42
                        $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
1406
1407 42
                        foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1408 42
                            if (isset($mapping['inherited']) || ($partialFieldSet && !in_array($fieldName, $partialFieldSet))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $partialFieldSet of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1409 42
                                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 42
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1427
                        }
1428
                    }
1429
                }
1430
1431 460
                $sql .= implode(', ', $sqlParts);
1432
        }
1433
1434 561
        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 17
    public function walkSubselect($subselect)
1449
    {
1450 17
        $useAliasesBefore  = $this->useSqlTableAliases;
1451 17
        $rootAliasesBefore = $this->rootAliases;
1452
1453 17
        $this->rootAliases = []; // reset the rootAliases for the subselect
1454 17
        $this->useSqlTableAliases = true;
1455
1456 17
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1457 17
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1458 17
        $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 17
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1461 17
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1462 17
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1463
1464 17
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1465 17
        $this->useSqlTableAliases = $useAliasesBefore;
1466
1467 17
        return $sql;
1468
    }
1469
1470
    /**
1471
     * {@inheritdoc}
1472
     */
1473 17
    public function walkSubselectFromClause($subselectFromClause)
1474
    {
1475 17
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1476 17
        $sqlParts               = [];
1477
1478 17
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1479 17
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1480
        }
1481
1482 17
        return ' FROM ' . implode(', ', $sqlParts);
1483
    }
1484
1485
    /**
1486
     * {@inheritdoc}
1487
     */
1488 17
    public function walkSimpleSelectClause($simpleSelectClause)
1489
    {
1490 17
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1491 17
            . $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 18
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1500
    {
1501 18
        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 20
    public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
1510
    {
1511 20
        $sqlSelectExpressions = [];
1512 20
        $objIndex             = $newObjectResultAlias?:$this->newObjectCounter++;
1513
1514 20
        foreach ($newObjectExpression->args as $argIndex => $e) {
1515 20
            $resultAlias = $this->scalarResultCounter++;
1516 20
            $columnAlias = $this->getSQLColumnAlias('sclr');
1517 20
            $fieldType   = 'string';
1518
1519
            switch (true) {
1520 20
                case ($e instanceof AST\NewObjectExpression):
1521
                    $sqlSelectExpressions[] = $e->dispatch($this);
1522
                    break;
1523
1524 20
                case ($e instanceof AST\Subselect):
1525
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1526
                    break;
1527
1528 20
                case ($e instanceof AST\PathExpression):
1529 20
                    $dqlAlias  = $e->identificationVariable;
1530 20
                    $qComp     = $this->queryComponents[$dqlAlias];
1531 20
                    $class     = $qComp['metadata'];
1532 20
                    $fieldType = $class->fieldMappings[$e->field]['type'];
1533
1534 20
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1535 20
                    break;
1536
1537 4
                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 3
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1553 3
                    break;
1554
            }
1555
1556 20
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1557 20
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1558
1559 20
            $this->rsm->newObjectMappings[$columnAlias] = [
1560 20
                'className' => $newObjectExpression->className,
1561 20
                'objIndex'  => $objIndex,
1562 20
                'argIndex'  => $argIndex
1563
            ];
1564
        }
1565
1566 20
        return implode(', ', $sqlSelectExpressions);
1567
    }
1568
1569
    /**
1570
     * {@inheritdoc}
1571
     */
1572 17
    public function walkSimpleSelectExpression($simpleSelectExpression)
1573
    {
1574 17
        $expr = $simpleSelectExpression->expression;
1575 17
        $sql  = ' ';
1576
1577
        switch (true) {
1578 17
            case ($expr instanceof AST\PathExpression):
1579 9
                $sql .= $this->walkPathExpression($expr);
1580 9
                break;
1581
1582 8
            case ($expr instanceof AST\Functions\AvgFunction):
1583 8
            case ($expr instanceof AST\Functions\CountFunction):
1584 8
            case ($expr instanceof AST\Functions\MaxFunction):
1585 8
            case ($expr instanceof AST\Functions\MinFunction):
1586 8
            case ($expr instanceof AST\Functions\SumFunction):
1587
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1588
1589
                $sql .= $expr->dispatch($this) . ' AS dctrn__' . $alias;
1590
                break;
1591
1592 8
            case ($expr instanceof AST\Subselect):
1593
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1594
1595
                $columnAlias = 'sclr' . $this->aliasCounter++;
1596
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1597
1598
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1599
                break;
1600
1601 8
            case ($expr instanceof AST\Functions\FunctionNode):
1602 7
            case ($expr instanceof AST\SimpleArithmeticExpression):
1603 7
            case ($expr instanceof AST\ArithmeticTerm):
1604 7
            case ($expr instanceof AST\ArithmeticFactor):
1605 7
            case ($expr instanceof AST\Literal):
1606 6
            case ($expr instanceof AST\NullIfExpression):
1607 6
            case ($expr instanceof AST\CoalesceExpression):
1608 6
            case ($expr instanceof AST\GeneralCaseExpression):
1609 4
            case ($expr instanceof AST\SimpleCaseExpression):
1610 6
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1611
1612 6
                $columnAlias = $this->getSQLColumnAlias('sclr');
1613 6
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1614
1615 6
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1616 6
                break;
1617
1618 2
            case ($expr instanceof AST\ParenthesisExpression):
1619
                $sql .= $this->walkParenthesisExpression($expr);
1620
                break;
1621
1622
            default: // IdentificationVariable
1623 2
                $sql .= $this->walkEntityIdentificationVariable($expr);
1624 2
                break;
1625
        }
1626
1627 17
        return $sql;
1628
    }
1629
1630
    /**
1631
     * {@inheritdoc}
1632
     */
1633 10
    public function walkAggregateExpression($aggExpression)
1634
    {
1635 10
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1636 10
            . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
0 ignored issues
show
Bug introduced by
It seems like $aggExpression->pathExpression can also be of type object<Doctrine\ORM\Query\AST\PathExpression>; however, Doctrine\ORM\Query\SqlWa...eArithmeticExpression() does only seem to accept object<Doctrine\ORM\Quer...leArithmeticExpression>, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1854
        }
1855
    }
1856
1857
    /**
1858
     * {@inheritdoc}
1859
     */
1860 5
    public function walkExistsExpression($existsExpr)
1861
    {
1862 5
        $sql = ($existsExpr->not) ? 'NOT ' : '';
1863
1864 5
        $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1865
1866 5
        return $sql;
1867
    }
1868
1869
    /**
1870
     * {@inheritdoc}
1871
     */
1872 6
    public function walkCollectionMemberExpression($collMemberExpr)
1873
    {
1874 6
        $sql = $collMemberExpr->not ? 'NOT ' : '';
1875 6
        $sql .= 'EXISTS (SELECT 1 FROM ';
1876
1877 6
        $entityExpr   = $collMemberExpr->entityExpression;
1878 6
        $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1879
1880 6
        $fieldName = $collPathExpr->field;
1881 6
        $dqlAlias  = $collPathExpr->identificationVariable;
1882
1883 6
        $class = $this->queryComponents[$dqlAlias]['metadata'];
1884
1885
        switch (true) {
1886
            // InputParameter
1887 6
            case ($entityExpr instanceof AST\InputParameter):
1888 4
                $dqlParamKey = $entityExpr->name;
1889 4
                $entitySql   = '?';
1890 4
                break;
1891
1892
            // SingleValuedAssociationPathExpression | IdentificationVariable
1893 2
            case ($entityExpr instanceof AST\PathExpression):
1894 2
                $entitySql = $this->walkPathExpression($entityExpr);
1895 2
                break;
1896
1897
            default:
1898
                throw new \BadMethodCallException("Not implemented");
1899
        }
1900
1901 6
        $assoc = $class->associationMappings[$fieldName];
1902
1903 6
        if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
1904 1
            $targetClass      = $this->em->getClassMetadata($assoc['targetEntity']);
1905 1
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1906 1
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1907
1908 1
            $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE ';
1909
1910 1
            $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
1911 1
            $sqlParts    = [];
1912
1913 1
            foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
1914 1
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform);
1915
1916 1
                $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
1917
            }
1918
1919 1
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1920 1
                if (isset($dqlParamKey)) {
1921 1
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1922
                }
1923
1924 1
                $sqlParts[] = $targetTableAlias . '.'  . $targetColumnName . ' = ' . $entitySql;
1925
            }
1926
1927 1
            $sql .= implode(' AND ', $sqlParts);
1928
        } else { // many-to-many
1929 5
            $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
1930
1931 5
            $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
1932 5
            $joinTable = $owningAssoc['joinTable'];
1933
1934
            // SQL table aliases
1935 5
            $joinTableAlias   = $this->getSQLTableAlias($joinTable['name']);
1936 5
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1937 5
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1938
1939
            // join to target table
1940 5
            $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias
1941 5
                . ' INNER JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' ON ';
1942
1943
            // join conditions
1944 5
            $joinColumns  = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns'];
1945 5
            $joinSqlParts = [];
1946
1947 5
            foreach ($joinColumns as $joinColumn) {
1948 5
                $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform);
1949
1950 5
                $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn;
1951
            }
1952
1953 5
            $sql .= implode(' AND ', $joinSqlParts);
1954 5
            $sql .= ' WHERE ';
1955
1956 5
            $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
1957 5
            $sqlParts    = [];
1958
1959 5
            foreach ($joinColumns as $joinColumn) {
1960 5
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform);
1961
1962 5
                $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn;
1963
            }
1964
1965 5
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1966 5
                if (isset($dqlParamKey)) {
1967 3
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1968
                }
1969
1970 5
                $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' IN (' . $entitySql . ')';
1971
            }
1972
1973 5
            $sql .= implode(' AND ', $sqlParts);
1974
        }
1975
1976 6
        return $sql . ')';
1977
    }
1978
1979
    /**
1980
     * {@inheritdoc}
1981
     */
1982 3
    public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
1983
    {
1984 3
        $sizeFunc = new AST\Functions\SizeFunction('size');
1985 3
        $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
1986
1987 3
        return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
1988
    }
1989
1990
    /**
1991
     * {@inheritdoc}
1992
     */
1993 9
    public function walkNullComparisonExpression($nullCompExpr)
1994
    {
1995 9
        $expression = $nullCompExpr->expression;
1996 9
        $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
1997
1998
        // Handle ResultVariable
1999 9
        if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
2000
            return $this->walkResultVariable($expression) . $comparison;
2001
        }
2002
2003
        // Handle InputParameter mapping inclusion to ParserResult
2004 9
        if ($expression instanceof AST\InputParameter) {
2005
            return $this->walkInputParameter($expression) . $comparison;
2006
        }
2007
2008 9
        return $expression->dispatch($this) . $comparison;
2009
    }
2010
2011
    /**
2012
     * {@inheritdoc}
2013
     */
2014 80
    public function walkInExpression($inExpr)
2015
    {
2016 80
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2017
2018 79
        $sql .= ($inExpr->subselect)
2019 7
            ? $this->walkSubselect($inExpr->subselect)
2020 79
            : implode(', ', array_map([$this, 'walkInParameter'], $inExpr->literals));
2021
2022 79
        $sql .= ')';
2023
2024 79
        return $sql;
2025
    }
2026
2027
    /**
2028
     * {@inheritdoc}
2029
     */
2030 9
    public function walkInstanceOfExpression($instanceOfExpr)
2031
    {
2032 9
        $sql = '';
2033
2034 9
        $dqlAlias = $instanceOfExpr->identificationVariable;
2035 9
        $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata'];
2036
2037 9
        if ($class->discriminatorColumn) {
2038 9
            $discrClass = $this->em->getClassMetadata($class->rootEntityName);
2039
        }
2040
2041 9
        if ($this->useSqlTableAliases) {
2042 9
            $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
2043
        }
2044
2045 9
        $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
2046
2047 9
        $sqlParameterList = [];
2048
2049 9
        foreach ($instanceOfExpr->value as $parameter) {
2050 9
            if ($parameter instanceof AST\InputParameter) {
2051 3
                $this->rsm->addMetadataParameterMapping($parameter->name, 'discriminatorValue');
2052
2053 3
                $sqlParameterList[] = $this->walkInputParameter($parameter);
2054
2055 3
                continue;
2056
            }
2057
2058
            // Get name from ClassMetadata to resolve aliases.
2059 6
            $entityClassName    = $this->em->getClassMetadata($parameter)->name;
2060 6
            $discriminatorValue = $class->discriminatorValue;
2061
2062 6
            if ($entityClassName !== $class->name) {
2063 5
                $discrMap = array_flip($class->discriminatorMap);
2064
2065 5
                if ( ! isset($discrMap[$entityClassName])) {
2066 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
2067
                }
2068
2069 4
                $discriminatorValue = $discrMap[$entityClassName];
2070
            }
2071
2072 5
            $sqlParameterList[] = $this->conn->quote($discriminatorValue);
2073
        }
2074
2075 8
        $sql .= '(' . implode(', ', $sqlParameterList) . ')';
2076
2077 8
        return $sql;
2078
    }
2079
2080
    /**
2081
     * {@inheritdoc}
2082
     */
2083 72
    public function walkInParameter($inParam)
2084
    {
2085 72
        return $inParam instanceof AST\InputParameter
2086 63
            ? $this->walkInputParameter($inParam)
2087 72
            : $this->walkLiteral($inParam);
2088
    }
2089
2090
    /**
2091
     * {@inheritdoc}
2092
     */
2093 125
    public function walkLiteral($literal)
2094
    {
2095 125
        switch ($literal->type) {
2096 125
            case AST\Literal::STRING:
2097 48
                return $this->conn->quote($literal->value);
2098
2099 89
            case AST\Literal::BOOLEAN:
2100 8
                return $this->conn->getDatabasePlatform()->convertBooleans('true' === strtolower($literal->value));
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->conn->getD...ower($literal->value)); (object|integer|double|string|null|array) is incompatible with the return type declared by the interface Doctrine\ORM\Query\TreeWalker::walkLiteral of type string.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2210
    }
2211
2212
    /**
2213
     * {@inheritdoc}
2214
     */
2215 326
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2216
    {
2217 326
        if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2218 276
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2219
        }
2220
2221 62
        return implode(' ', array_map([$this, 'walkArithmeticTerm'], $simpleArithmeticExpr->arithmeticTerms));
2222
    }
2223
2224
    /**
2225
     * {@inheritdoc}
2226
     */
2227 347
    public function walkArithmeticTerm($term)
2228
    {
2229 347
        if (is_string($term)) {
2230 11
            return (isset($this->queryComponents[$term]))
2231 2
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2232 11
                : $term;
2233
        }
2234
2235
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2236
        // if only one ArithmeticFactor is defined
2237 346
        if ( ! ($term instanceof AST\ArithmeticTerm)) {
2238 324
            return $this->walkArithmeticFactor($term);
2239
        }
2240
2241 41
        return implode(' ', array_map([$this, 'walkArithmeticFactor'], $term->arithmeticFactors));
2242
    }
2243
2244
    /**
2245
     * {@inheritdoc}
2246
     */
2247 347
    public function walkArithmeticFactor($factor)
2248
    {
2249 347
        if (is_string($factor)) {
2250 41
            return (isset($this->queryComponents[$factor]))
2251
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2252 41
                : $factor;
2253
        }
2254
2255
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2256
        // if only one ArithmeticPrimary is defined
2257 347
        if ( ! ($factor instanceof AST\ArithmeticFactor)) {
2258 346
            return $this->walkArithmeticPrimary($factor);
2259
        }
2260
2261 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2262
2263 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2264
    }
2265
2266
    /**
2267
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2268
     *
2269
     * @param mixed $primary
2270
     *
2271
     * @return string The SQL.
2272
     */
2273 347
    public function walkArithmeticPrimary($primary)
2274
    {
2275 347
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2276
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2277
        }
2278
2279 347
        if ($primary instanceof AST\Node) {
2280 347
            return $primary->dispatch($this);
2281
        }
2282
2283
        return $this->walkEntityIdentificationVariable($primary);
2284
    }
2285
2286
    /**
2287
     * {@inheritdoc}
2288
     */
2289 17
    public function walkStringPrimary($stringPrimary)
2290
    {
2291 17
        return (is_string($stringPrimary))
2292
            ? $this->conn->quote($stringPrimary)
2293 17
            : $stringPrimary->dispatch($this);
2294
    }
2295
2296
    /**
2297
     * {@inheritdoc}
2298
     */
2299 11
    public function walkResultVariable($resultVariable)
2300
    {
2301 11
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2302
2303 11
        if (is_array($resultAlias)) {
2304 1
            return implode(', ', $resultAlias);
2305
        }
2306
2307 10
        return $resultAlias;
2308
    }
2309
}
2310