Completed
Pull Request — master (#6417)
by Luís
19:07
created

SqlWalker::walkArithmeticTerm()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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