Completed
Pull Request — master (#5912)
by Reen
10:05
created

SqlWalker::walkOrderByItem()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 16
c 0
b 0
f 0
ccs 10
cts 10
cp 1
rs 9.4285
cc 3
eloc 10
nc 4
nop 1
crap 3
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 = array();
113
114
    /**
115
     * Map from result variable names to their SQL column alias names.
116
     *
117
     * @var array
118
     */
119
    private $scalarResultAliasMap = array();
120
121
    /**
122
     * Map from Table-Alias + Column-Name to OrderBy-Direction.
123
     *
124
     * @var array
125
     */
126
    private $orderedColumnsMap = array();
127
128
    /**
129
     * Map from DQL-Alias + Field-Name to SQL Column Alias.
130
     *
131
     * @var array
132
     */
133
    private $scalarFields = array();
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 = array();
148
149
    /**
150
     * The DQL alias of the root class of the currently traversed query.
151
     *
152
     * @var array
153
     */
154
    private $rootAliases = array();
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 679
    public function __construct($query, $parserResult, array $queryComponents)
182
    {
183 679
        $this->query            = $query;
184 679
        $this->parserResult     = $parserResult;
185 679
        $this->queryComponents  = $queryComponents;
186 679
        $this->rsm              = $parserResult->getResultSetMapping();
187 679
        $this->em               = $query->getEntityManager();
188 679
        $this->conn             = $this->em->getConnection();
189 679
        $this->platform         = $this->conn->getDatabasePlatform();
190 679
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
191 679
    }
192
193
    /**
194
     * Gets the Query instance used by the walker.
195
     *
196
     * @return Query.
0 ignored issues
show
Documentation introduced by
The doc-type Query. could not be parsed: Unknown type name "Query." at position 0. (view supported doc-types)

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

Loading history...
197
     */
198
    public function getQuery()
199
    {
200
        return $this->query;
201
    }
202
203
    /**
204
     * Gets the Connection used by the walker.
205
     *
206
     * @return \Doctrine\DBAL\Connection
207
     */
208 35
    public function getConnection()
209
    {
210 35
        return $this->conn;
211
    }
212
213
    /**
214
     * Gets the EntityManager used by the walker.
215
     *
216
     * @return \Doctrine\ORM\EntityManager
217
     */
218 23
    public function getEntityManager()
219
    {
220 23
        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 18
    public function getQueryComponent($dqlAlias)
231
    {
232 18
        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 = array('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 673
    public function getExecutor($AST)
261
    {
262
        switch (true) {
263 673
            case ($AST instanceof AST\DeleteStatement):
264 39
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
265
266 39
                return ($primaryClass->isInheritanceTypeJoined())
267 2
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
268 39
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
269
270 613
            case ($AST instanceof AST\UpdateStatement):
271 29
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
272
273 29
                return ($primaryClass->isInheritanceTypeJoined())
274 4
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
275 29
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
276
277
            default:
278 613
                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 625
    public function getSQLTableAlias($tableName, $dqlAlias = '')
291
    {
292 625
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
293
294 625
        if ( ! isset($this->tableAliasMap[$tableName])) {
295 625
            $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
296 625
                . $this->tableAliasCounter++ . '_';
297
        }
298
299 625
        return $this->tableAliasMap[$tableName];
300
    }
301
302
    /**
303
     * Forces the SqlWalker to use a specific alias for a table name, rather than
304
     * generating an alias on its own.
305
     *
306
     * @param string $tableName
307
     * @param string $alias
308
     * @param string $dqlAlias
309
     *
310
     * @return string
311
     */
312 65
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
313
    {
314 65
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
315
316 65
        $this->tableAliasMap[$tableName] = $alias;
317
318 65
        return $alias;
319
    }
320
321
    /**
322
     * Gets an SQL column alias for a column name.
323
     *
324
     * @param string $columnName
325
     *
326
     * @return string
327
     */
328 613
    public function getSQLColumnAlias($columnName)
329
    {
330 613
        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 91
    private function _generateClassTableInheritanceJoins($class, $dqlAlias)
343
    {
344 91
        $sql = '';
345
346 91
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
347
348
        // INNER JOIN parent class tables
349 91
        foreach ($class->parentClasses as $parentClassName) {
350 66
            $parentClass = $this->em->getClassMetadata($parentClassName);
351 66
            $tableAlias  = $this->getSQLTableAlias($parentClass->getTableName(), $dqlAlias);
352
353
            // If this is a joined association we must use left joins to preserve the correct result.
354 66
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
355 66
            $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
356
357 66
            $sqlParts = array();
358
359 66
            foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
360 66
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
361
            }
362
363
            // Add filters on the root class
364 66
            if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
365 1
                $sqlParts[] = $filterSql;
366
            }
367
368 66
            $sql .= implode(' AND ', $sqlParts);
369
        }
370
371
        // Ignore subclassing inclusion if partial objects is disallowed
372 91
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
373 21
            return $sql;
374
        }
375
376
        // LEFT JOIN child class tables
377 70
        foreach ($class->subClasses as $subClassName) {
378 31
            $subClass   = $this->em->getClassMetadata($subClassName);
379 31
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
380
381 31
            $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
382
383 31
            $sqlParts = array();
384
385 31
            foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
386 31
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
387
            }
388
389 31
            $sql .= implode(' AND ', $sqlParts);
390
        }
391
392 70
        return $sql;
393
    }
394
395
    /**
396
     * @return string
397
     */
398 606
    private function _generateOrderedCollectionOrderByItems()
399
    {
400 606
        $orderedColumns = array();
401
402 606
        foreach ($this->selectedClasses as $selectedClass) {
403 470
            $dqlAlias  = $selectedClass['dqlAlias'];
404 470
            $qComp     = $this->queryComponents[$dqlAlias];
405
406 470
            if ( ! isset($qComp['relation']['orderBy'])) {
407 470
                continue;
408
            }
409
410 6
            $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
411
412 6
            foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
413 6
                $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
414 6
                $tableName  = ($qComp['metadata']->isInheritanceTypeJoined())
415 1
                    ? $persister->getOwningTable($fieldName)
416 6
                    : $qComp['metadata']->getTableName();
417
418 6
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
419
420
                // OrderByClause should replace an ordered relation. see - DDC-2475
421 6
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
422 1
                    continue;
423
                }
424
425 6
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
426 6
                $orderedColumns[] = $orderedColumn . ' ' . $orientation;
427
            }
428
        }
429
430 606
        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 667
    private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
441
    {
442 667
        $sqlParts = array();
443
444 667
        foreach ($dqlAliases as $dqlAlias) {
445 667
            $class = $this->queryComponents[$dqlAlias]['metadata'];
446
447 667
            if ( ! $class->isInheritanceTypeSingleTable()) continue;
448
449 36
            $conn   = $this->em->getConnection();
450 36
            $values = array();
451
452 36
            if ($class->discriminatorValue !== null) { // discriminators can be 0
453 18
                $values[] = $conn->quote($class->discriminatorValue);
454
            }
455
456 36
            foreach ($class->subClasses as $subclassName) {
457 26
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
458
            }
459
460 36
            $sqlTableAlias = ($this->useSqlTableAliases)
461 31
                ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
462 36
                : '';
463
464 36
            $sqlParts[] = $sqlTableAlias . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
465
        }
466
467 667
        $sql = implode(' AND ', $sqlParts);
468
469 667
        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 317
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
481
    {
482 317
        if (!$this->em->hasFilters()) {
483 279
            return '';
484
        }
485
486 43
        switch($targetEntity->inheritanceType) {
487 43
            case ClassMetadata::INHERITANCE_TYPE_NONE:
488 33
                break;
489 10
            case ClassMetadata::INHERITANCE_TYPE_JOINED:
490
                // The classes in the inheritance will be added to the query one by one,
491
                // but only the root node is getting filtered
492 6
                if ($targetEntity->name !== $targetEntity->rootEntityName) {
493 4
                    return '';
494
                }
495 6
                break;
496 4
            case ClassMetadata::INHERITANCE_TYPE_SINGLE_TABLE:
497
                // With STI the table will only be queried once, make sure that the filters
498
                // are added to the root entity
499 4
                $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
500 4
                break;
501
            default:
502
                //@todo: throw exception?
503
                return '';
504
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
505
        }
506
507 43
        $filterClauses = array();
508 43
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
509 10
            if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
510 10
                $filterClauses[] = '(' . $filterExpr . ')';
511
            }
512
        }
513
514 43
        return implode(' AND ', $filterClauses);
515
    }
516
517
    /**
518
     * {@inheritdoc}
519
     */
520 613
    public function walkSelectStatement(AST\SelectStatement $AST)
521
    {
522 613
        $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...
523 613
        $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...
524 613
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
525 613
        $sql      = $this->walkSelectClause($AST->selectClause)
526 613
            . $this->walkFromClause($AST->fromClause)
527 610
            . $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...
528
529 607
        if ($AST->groupByClause) {
530 23
            $sql .= $this->walkGroupByClause($AST->groupByClause);
531
        }
532
533 607
        if ($AST->havingClause) {
534 14
            $sql .= $this->walkHavingClause($AST->havingClause);
535
        }
536
537 607
        if ($AST->orderByClause) {
538 142
            $sql .= $this->walkOrderByClause($AST->orderByClause);
539
        }
540
541 606
        if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) {
542 6
            $sql .= ' ORDER BY ' . $orderBySql;
543
        }
544
545 606
        if ($limit !== null || $offset !== null) {
546 38
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
547
        }
548
549 606
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
550 601
            return $sql;
551
        }
552
553 5
        if ($lockMode === LockMode::PESSIMISTIC_READ) {
554 3
            return $sql . ' ' . $this->platform->getReadLockSQL();
555
        }
556
557 2
        if ($lockMode === LockMode::PESSIMISTIC_WRITE) {
558 1
            return $sql . ' ' . $this->platform->getWriteLockSQL();
559
        }
560
561 1
        if ($lockMode !== LockMode::OPTIMISTIC) {
562
            throw QueryException::invalidLockMode();
563
        }
564
565 1
        foreach ($this->selectedClasses as $selectedClass) {
566 1
            if ( ! $selectedClass['class']->isVersioned) {
567 1
                throw OptimisticLockException::lockFailed($selectedClass['class']->name);
568
            }
569
        }
570
571
        return $sql;
572
    }
573
574
    /**
575
     * {@inheritdoc}
576
     */
577 25
    public function walkUpdateStatement(AST\UpdateStatement $AST)
578
    {
579 25
        $this->useSqlTableAliases = false;
580 25
        $this->rsm->isSelect      = false;
581
582 25
        return $this->walkUpdateClause($AST->updateClause)
583 25
            . $this->walkWhereClause($AST->whereClause);
0 ignored issues
show
Bug introduced by
It seems like $AST->whereClause can be null; however, walkWhereClause() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
584
    }
585
586
    /**
587
     * {@inheritdoc}
588
     */
589 37
    public function walkDeleteStatement(AST\DeleteStatement $AST)
590
    {
591 37
        $this->useSqlTableAliases = false;
592 37
        $this->rsm->isSelect      = false;
593
594 37
        return $this->walkDeleteClause($AST->deleteClause)
595 37
            . $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...
596
    }
597
598
    /**
599
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
600
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
601
     *
602
     * @param string $identVariable
603
     *
604
     * @return string
605
     */
606 2
    public function walkEntityIdentificationVariable($identVariable)
607
    {
608 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
609 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
610 2
        $sqlParts   = array();
611
612 2
        foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
613 2
            $sqlParts[] = $tableAlias . '.' . $columnName;
614
        }
615
616 2
        return implode(', ', $sqlParts);
617
    }
618
619
    /**
620
     * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
621
     *
622
     * @param string $identificationVariable
623
     * @param string $fieldName
624
     *
625
     * @return string The SQL.
626
     */
627 415
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
628
    {
629 415
        $class = $this->queryComponents[$identificationVariable]['metadata'];
630
631
        if (
632 415
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
633 415
            isset($class->fieldMappings[$fieldName]['inherited'])
634
        ) {
635 37
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
636
        }
637
638 415
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
639
    }
640
641
    /**
642
     * {@inheritdoc}
643
     */
644 484
    public function walkPathExpression($pathExpr)
645
    {
646 484
        $sql = '';
647
648 484
        switch ($pathExpr->type) {
649 484
            case AST\PathExpression::TYPE_STATE_FIELD:
650 463
                $fieldName = $pathExpr->field;
651 463
                $dqlAlias = $pathExpr->identificationVariable;
652 463
                $class = $this->queryComponents[$dqlAlias]['metadata'];
653
654 463
                if ($this->useSqlTableAliases) {
655 415
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
656
                }
657
658 463
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
659 463
                break;
660
661 62
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
662
                // 1- the owning side:
663
                //    Just use the foreign key, i.e. u.group_id
664 62
                $fieldName = $pathExpr->field;
665 62
                $dqlAlias = $pathExpr->identificationVariable;
666 62
                $class = $this->queryComponents[$dqlAlias]['metadata'];
667
668 62
                if (isset($class->associationMappings[$fieldName]['inherited'])) {
669 2
                    $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
670
                }
671
672 62
                $assoc = $class->associationMappings[$fieldName];
673
674 62
                if ( ! $assoc['isOwningSide']) {
675 2
                    throw QueryException::associationPathInverseSideNotSupported();
676
                }
677
678
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
679 60
                if (count($assoc['sourceToTargetKeyColumns']) > 1) {
680 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
681
                }
682
683 59
                if ($this->useSqlTableAliases) {
684 56
                    $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
685
                }
686
687 59
                $sql .= reset($assoc['targetToSourceKeyColumns']);
688 59
                break;
689
690
            default:
691
                throw QueryException::invalidPathExpression($pathExpr);
692
        }
693
694 481
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 613
    public function walkSelectClause($selectClause)
701
    {
702 613
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
703 613
        $sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions));
704
705 613
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
706 1
            $this->query->setHint(self::HINT_DISTINCT, true);
707
        }
708
709 613
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
710 450
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
711
            ||
712 288
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
713 613
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
714
715 613
        foreach ($this->selectedClasses as $selectedClass) {
716 477
            $class       = $selectedClass['class'];
717 477
            $dqlAlias    = $selectedClass['dqlAlias'];
718 477
            $resultAlias = $selectedClass['resultAlias'];
719
720
            // Register as entity or joined entity result
721 477
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
722 477
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
723
            } else {
724 157
                $this->rsm->addJoinedEntityResult(
725 157
                    $class->name,
726
                    $dqlAlias,
727 157
                    $this->queryComponents[$dqlAlias]['parent'],
728 157
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
729
                );
730
            }
731
732 477
            if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
733
                // Add discriminator columns to SQL
734 85
                $rootClass   = $this->em->getClassMetadata($class->rootEntityName);
735 85
                $tblAlias    = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
736 85
                $discrColumn = $rootClass->discriminatorColumn;
737 85
                $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
738
739 85
                $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
740
741 85
                $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
742 85
                $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], false, $discrColumn['type']);
743
            }
744
745
            // Add foreign key columns to SQL, if necessary
746 477
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
747 180
                continue;
748
            }
749
750
            // Add foreign key columns of class and also parent classes
751 346
            foreach ($class->associationMappings as $assoc) {
752 310
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
753 263
                    continue;
754 278
                } else if ( !$addMetaColumns && !isset($assoc['id'])) {
755
                    continue;
756
                }
757
758 278
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
759 278
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
760 278
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
761 278
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
762
763 278
                foreach ($assoc['joinColumns'] as $joinColumn) {
764 278
                    $columnName  = $joinColumn['name'];
765 278
                    $columnAlias = $this->getSQLColumnAlias($columnName);
766 278
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
767
768 278
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
769
770 278
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
771
                }
772
            }
773
774
            // Add foreign key columns to SQL, if necessary
775 346
            if ( ! $addMetaColumns) {
776 8
                continue;
777
            }
778
779
            // Add foreign key columns of subclasses
780 341
            foreach ($class->subClasses as $subClassName) {
781 27
                $subClass      = $this->em->getClassMetadata($subClassName);
782 27
                $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
783
784 27
                foreach ($subClass->associationMappings as $assoc) {
785
                    // Skip if association is inherited
786 24
                    if (isset($assoc['inherited'])) continue;
787
788 15
                    if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
789 13
                        $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
790
791 13
                        foreach ($assoc['joinColumns'] as $joinColumn) {
792 13
                            $columnName  = $joinColumn['name'];
793 13
                            $columnAlias = $this->getSQLColumnAlias($columnName);
794 13
                            $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
795
796 13
                            $sqlSelectExpressions[] = $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
797
798 341
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
799
                        }
800
                    }
801
                }
802
            }
803
        }
804
805 613
        $sql .= implode(', ', $sqlSelectExpressions);
806
807 613
        return $sql;
808
    }
809
810
    /**
811
     * {@inheritdoc}
812
     */
813 615
    public function walkFromClause($fromClause)
814
    {
815 615
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
816 615
        $sqlParts = array();
817
818 615
        foreach ($identificationVarDecls as $identificationVariableDecl) {
819 615
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
820
        }
821
822 612
        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 616
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
833
    {
834 616
        $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 616
        if ($identificationVariableDecl->indexBy) {
837 5
            $this->walkIndexBy($identificationVariableDecl->indexBy);
838
        }
839
840 616
        foreach ($identificationVariableDecl->joins as $join) {
841 243
            $sql .= $this->walkJoin($join);
842
        }
843
844 613
        return $sql;
845
    }
846
847
    /**
848
     * Walks down a IndexBy AST node.
849
     *
850
     * @param AST\IndexBy $indexBy
851
     *
852
     * @return void
853
     */
854 8
    public function walkIndexBy($indexBy)
855
    {
856 8
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
857 8
        $alias          = $pathExpression->identificationVariable;
858 8
        $field          = $pathExpression->field;
859
860 8
        if (isset($this->scalarFields[$alias][$field])) {
861
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
862
863
            return;
864
        }
865
866 8
        $this->rsm->addIndexBy($alias, $field);
867 8
    }
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 616
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
877
    {
878 616
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
879 616
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
880
881 616
        if ($rangeVariableDeclaration->isRoot) {
882 616
            $this->rootAliases[] = $dqlAlias;
883
        }
884
885 616
        $sql = $this->platform->appendLockHint(
886 616
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
887 616
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
888 616
            $this->query->getHint(Query::HINT_LOCK_MODE)
889
        );
890
891 616
        if ($class->isInheritanceTypeJoined()) {
892 87
            $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
893
        }
894
895 616
        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 226
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
910
    {
911 226
        $sql = '';
912
913 226
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
914 226
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
915 226
        $indexBy                   = $joinAssociationDeclaration->indexBy;
916
917 226
        $relation        = $this->queryComponents[$joinedDqlAlias]['relation'];
918 226
        $targetClass     = $this->em->getClassMetadata($relation['targetEntity']);
919 226
        $sourceClass     = $this->em->getClassMetadata($relation['sourceEntity']);
920 226
        $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
921
922 226
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
923 226
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
924
925
        // Ensure we got the owning side, since it has all mapping info
926 226
        $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
927
928 226
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
929 4
            if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
930 3
                throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
931
            }
932
        }
933
934 223
        $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 223
            case ($assoc['type'] & ClassMetadata::TO_ONE):
941 176
                $conditions = array();
942
943 176
                foreach ($assoc['joinColumns'] as $joinColumn) {
944 176
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
945 176
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
946
947 176
                    if ($relation['isOwningSide']) {
948 101
                        $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
949
950 101
                        continue;
951
                    }
952
953 107
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
954
                }
955
956
                // Apply remaining inheritance restrictions
957 176
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
958
959 176
                if ($discrSql) {
960 2
                    $conditions[] = $discrSql;
961
                }
962
963
                // Apply the filters
964 176
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
965
966 176
                if ($filterExpr) {
967 1
                    $conditions[] = $filterExpr;
968
                }
969
970
                $targetTableJoin = array(
971 176
                    'table' => $targetTableName . ' ' . $targetTableAlias,
972 176
                    'condition' => implode(' AND ', $conditions),
973
                );
974 176
                break;
975
976 57
            case ($assoc['type'] == ClassMetadata::MANY_TO_MANY):
977
                // Join relation table
978 57
                $joinTable      = $assoc['joinTable'];
979 57
                $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
980 57
                $joinTableName  = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
981
982 57
                $conditions      = array();
983 57
                $relationColumns = ($relation['isOwningSide'])
984 48
                    ? $assoc['joinTable']['joinColumns']
985 57
                    : $assoc['joinTable']['inverseJoinColumns'];
986
987 57
                foreach ($relationColumns as $joinColumn) {
988 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
989 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
990
991 57
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
992
                }
993
994 57
                $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
995
996
                // Join target table
997 57
                $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
998
999 57
                $conditions      = array();
1000 57
                $relationColumns = ($relation['isOwningSide'])
1001 48
                    ? $assoc['joinTable']['inverseJoinColumns']
1002 57
                    : $assoc['joinTable']['joinColumns'];
1003
1004 57
                foreach ($relationColumns as $joinColumn) {
1005 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1006 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1007
1008 57
                    $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1009
                }
1010
1011
                // Apply remaining inheritance restrictions
1012 57
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
1013
1014 57
                if ($discrSql) {
1015 1
                    $conditions[] = $discrSql;
1016
                }
1017
1018
                // Apply the filters
1019 57
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1020
1021 57
                if ($filterExpr) {
1022 1
                    $conditions[] = $filterExpr;
1023
                }
1024
1025
                $targetTableJoin = array(
1026 57
                    'table' => $targetTableName . ' ' . $targetTableAlias,
1027 57
                    'condition' => implode(' AND ', $conditions),
1028
                );
1029 57
                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 223
        $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1037
1038 223
        if ($targetClass->isInheritanceTypeJoined()) {
1039 9
            $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 9
            if ($withCondition) {
1042 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1043
            } else {
1044 9
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1045
            }
1046
        } else {
1047 214
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1048
        }
1049
1050 223
        if ($withCondition) {
1051 5
            $sql .= ' AND ' . $withCondition;
1052
        }
1053
1054
        // Apply the indexes
1055 223
        if ($indexBy) {
1056
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1057 5
            $this->walkIndexBy($indexBy);
1058 218
        } else if (isset($relation['indexBy'])) {
1059 3
            $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
1060
        }
1061
1062 223
        return $sql;
1063
    }
1064
1065
    /**
1066
     * {@inheritdoc}
1067
     */
1068 64
    public function walkFunction($function)
1069
    {
1070 64
        return $function->getSql($this);
1071
    }
1072
1073
    /**
1074
     * {@inheritdoc}
1075
     */
1076 153
    public function walkOrderByClause($orderByClause)
1077
    {
1078 153
        $orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems);
1079
1080 152
        if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
1081
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1082
        }
1083
1084 152
        return ' ORDER BY ' . implode(', ', $orderByItems);
1085
    }
1086
1087
    /**
1088
     * {@inheritdoc}
1089
     */
1090 171
    public function walkOrderByItem($orderByItem)
1091
    {
1092 171
        $type = strtoupper($orderByItem->type);
1093 171
        $expr = $orderByItem->expression;
1094 171
        $sql  = ($expr instanceof AST\Node)
1095 164
            ? $expr->dispatch($this)
1096 170
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1097
1098 170
        $this->orderedColumnsMap[$sql] = $type;
1099
1100 170
        if ($expr instanceof AST\Subselect) {
1101 2
            return '(' . $sql . ') ' . $type;
1102
        }
1103
1104 168
        return $sql . ' ' . $type;
1105
    }
1106
1107
    /**
1108
     * {@inheritdoc}
1109
     */
1110 14
    public function walkHavingClause($havingClause)
1111
    {
1112 14
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1113
    }
1114
1115
    /**
1116
     * {@inheritdoc}
1117
     */
1118 243
    public function walkJoin($join)
1119
    {
1120 243
        $joinType        = $join->joinType;
1121 243
        $joinDeclaration = $join->joinAssociationDeclaration;
1122
1123 243
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1124 54
            ? ' LEFT JOIN '
1125 243
            : ' INNER JOIN ';
1126
1127
        switch (true) {
1128 243
            case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration):
1129 17
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1130 17
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1131 17
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1132 17
                $conditions = [];
1133
1134 17
                if ($join->conditionalExpression) {
1135 15
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1136
                }
1137
1138 17
                $condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER)
1139 3
                    ? ' AND '
1140 17
                    : ' ON ';
1141
1142 17
                $sql .= $this->walkRangeVariableDeclaration($joinDeclaration);
1143
1144
                // Apply remaining inheritance restrictions
1145 17
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($dqlAlias));
1146
1147 17
                if ($discrSql) {
1148 3
                    $conditions[] = $discrSql;
1149
                }
1150
1151
                // Apply the filters
1152 17
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1153
1154 17
                if ($filterExpr) {
1155
                    $conditions[] = $filterExpr;
1156
                }
1157
1158 17
                if ($conditions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $conditions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1159 15
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1160
                }
1161
1162 17
                break;
1163
1164
            case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration):
1165 226
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1166 223
                break;
1167
        }
1168
1169 240
        return $sql;
1170
    }
1171
1172
    /**
1173
     * Walks down a CaseExpression AST node and generates the corresponding SQL.
1174
     *
1175
     * @param AST\CoalesceExpression|AST\NullIfExpression|AST\GeneralCaseExpression|AST\SimpleCaseExpression $expression
1176
     *
1177
     * @return string The SQL.
1178
     */
1179
    public function walkCaseExpression($expression)
1180
    {
1181
        switch (true) {
1182
            case ($expression instanceof AST\CoalesceExpression):
1183
                return $this->walkCoalesceExpression($expression);
1184
1185
            case ($expression instanceof AST\NullIfExpression):
1186
                return $this->walkNullIfExpression($expression);
1187
1188
            case ($expression instanceof AST\GeneralCaseExpression):
1189
                return $this->walkGeneralCaseExpression($expression);
1190
1191
            case ($expression instanceof AST\SimpleCaseExpression):
1192
                return $this->walkSimpleCaseExpression($expression);
1193
1194
            default:
1195
                return '';
1196
        }
1197
    }
1198
1199
    /**
1200
     * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1201
     *
1202
     * @param AST\CoalesceExpression $coalesceExpression
1203
     *
1204
     * @return string The SQL.
1205
     */
1206 2
    public function walkCoalesceExpression($coalesceExpression)
1207
    {
1208 2
        $sql = 'COALESCE(';
1209
1210 2
        $scalarExpressions = array();
1211
1212 2
        foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1213 2
            $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1214
        }
1215
1216 2
        $sql .= implode(', ', $scalarExpressions) . ')';
1217
1218 2
        return $sql;
1219
    }
1220
1221
    /**
1222
     * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1223
     *
1224
     * @param AST\NullIfExpression $nullIfExpression
1225
     *
1226
     * @return string The SQL.
1227
     */
1228 3
    public function walkNullIfExpression($nullIfExpression)
1229
    {
1230 3
        $firstExpression = is_string($nullIfExpression->firstExpression)
1231
            ? $this->conn->quote($nullIfExpression->firstExpression)
1232 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1233
1234 3
        $secondExpression = is_string($nullIfExpression->secondExpression)
1235
            ? $this->conn->quote($nullIfExpression->secondExpression)
1236 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1237
1238 3
        return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1239
    }
1240
1241
    /**
1242
     * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1243
     *
1244
     * @param AST\GeneralCaseExpression $generalCaseExpression
1245
     *
1246
     * @return string The SQL.
1247
     */
1248 9
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1249
    {
1250 9
        $sql = 'CASE';
1251
1252 9
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1253 9
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1254 9
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1255
        }
1256
1257 9
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1258
1259 9
        return $sql;
1260
    }
1261
1262
    /**
1263
     * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1264
     *
1265
     * @param AST\SimpleCaseExpression $simpleCaseExpression
1266
     *
1267
     * @return string The SQL.
1268
     */
1269 5
    public function walkSimpleCaseExpression($simpleCaseExpression)
1270
    {
1271 5
        $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1272
1273 5
        foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1274 5
            $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1275 5
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1276
        }
1277
1278 5
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1279
1280 5
        return $sql;
1281
    }
1282
1283
    /**
1284
     * {@inheritdoc}
1285
     */
1286 613
    public function walkSelectExpression($selectExpression)
1287
    {
1288 613
        $sql    = '';
1289 613
        $expr   = $selectExpression->expression;
1290 613
        $hidden = $selectExpression->hiddenAliasResultVariable;
1291
1292
        switch (true) {
1293 613
            case ($expr instanceof AST\PathExpression):
1294 101
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1295
                    throw QueryException::invalidPathExpression($expr);
1296
                }
1297
1298 101
                $fieldName = $expr->field;
1299 101
                $dqlAlias  = $expr->identificationVariable;
1300 101
                $qComp     = $this->queryComponents[$dqlAlias];
1301 101
                $class     = $qComp['metadata'];
1302
1303 101
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1304 101
                $tableName   = ($class->isInheritanceTypeJoined())
1305 11
                    ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1306 101
                    : $class->getTableName();
1307
1308 101
                $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1309 101
                $fieldMapping  = $class->fieldMappings[$fieldName];
1310 101
                $columnName    = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1311 101
                $columnAlias   = $this->getSQLColumnAlias($fieldMapping['columnName']);
1312 101
                $col           = $sqlTableAlias . '.' . $columnName;
1313
1314 101
                if (isset($fieldMapping['requireSQLConversion'])) {
1315 2
                    $type = Type::getType($fieldMapping['type']);
1316 2
                    $col  = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1317
                }
1318
1319 101
                $sql .= $col . ' AS ' . $columnAlias;
1320
1321 101
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1322
1323 101
                if ( ! $hidden) {
1324 101
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
1325 101
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1326
                }
1327
1328 101
                break;
1329
1330
            case ($expr instanceof AST\AggregateExpression):
1331
            case ($expr instanceof AST\Functions\FunctionNode):
1332 10
            case ($expr instanceof AST\SimpleArithmeticExpression):
1333 22
            case ($expr instanceof AST\ArithmeticTerm):
1334 64
            case ($expr instanceof AST\ArithmeticFactor):
1335 28
            case ($expr instanceof AST\ParenthesisExpression):
1336
            case ($expr instanceof AST\Literal):
1337 37
            case ($expr instanceof AST\NullIfExpression):
1338 37
            case ($expr instanceof AST\CoalesceExpression):
1339 9
            case ($expr instanceof AST\GeneralCaseExpression):
1340 43
            case ($expr instanceof AST\SimpleCaseExpression):
1341 105
                $columnAlias = $this->getSQLColumnAlias('sclr');
1342 105
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1343
1344 105
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1345
1346 105
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1347
1348 105
                if ( ! $hidden) {
1349
                    // We cannot resolve field type here; assume 'string'.
1350 105
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1351
                }
1352 105
                break;
1353
1354
            case ($expr instanceof AST\Subselect):
1355 15
                $columnAlias = $this->getSQLColumnAlias('sclr');
1356 15
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1357
1358 15
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1359
1360 15
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1361
1362 15
                if ( ! $hidden) {
1363
                    // We cannot resolve field type here; assume 'string'.
1364 13
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1365
                }
1366 15
                break;
1367
1368 477
            case ($expr instanceof AST\NewObjectExpression):
1369 22
                $sql .= $this->walkNewObject($expr,$selectExpression->fieldIdentificationVariable);
1370 22
                break;
1371
1372
            default:
1373
                // IdentificationVariable or PartialObjectExpression
1374 477
                if ($expr instanceof AST\PartialObjectExpression) {
1375 16
                    $dqlAlias = $expr->identificationVariable;
1376 16
                    $partialFieldSet = $expr->partialFieldSet;
1377
                } else {
1378 472
                    $dqlAlias = $expr;
1379 472
                    $partialFieldSet = array();
1380
                }
1381
1382 477
                $queryComp   = $this->queryComponents[$dqlAlias];
1383 477
                $class       = $queryComp['metadata'];
1384 477
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1385
1386 477
                if ( ! isset($this->selectedClasses[$dqlAlias])) {
1387 477
                    $this->selectedClasses[$dqlAlias] = array(
1388 477
                        'class'       => $class,
1389 477
                        'dqlAlias'    => $dqlAlias,
1390 477
                        'resultAlias' => $resultAlias
1391
                    );
1392
                }
1393
1394 477
                $sqlParts = array();
1395
1396
                // Select all fields from the queried class
1397 477
                foreach ($class->fieldMappings as $fieldName => $mapping) {
1398 476
                    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...
1399 14
                        continue;
1400
                    }
1401
1402 475
                    $tableName = (isset($mapping['inherited']))
1403 51
                        ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1404 475
                        : $class->getTableName();
1405
1406 475
                    $sqlTableAlias    = $this->getSQLTableAlias($tableName, $dqlAlias);
1407 475
                    $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1408 475
                    $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1409
1410 475
                    $col = $sqlTableAlias . '.' . $quotedColumnName;
1411
1412 475
                    if (isset($mapping['requireSQLConversion'])) {
1413 5
                        $type = Type::getType($mapping['type']);
1414 5
                        $col = $type->convertToPHPValueSQL($col, $this->platform);
1415
                    }
1416
1417 475
                    $sqlParts[] = $col . ' AS '. $columnAlias;
1418
1419 475
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1420
1421 475
                    $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $class->name);
1422
                }
1423
1424
                // Add any additional fields of subclasses (excluding inherited fields)
1425
                // 1) on Single Table Inheritance: always, since its marginal overhead
1426
                // 2) on Class Table Inheritance only if partial objects are disallowed,
1427
                //    since it requires outer joining subtables.
1428 477
                if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1429 383
                    foreach ($class->subClasses as $subClassName) {
1430 37
                        $subClass      = $this->em->getClassMetadata($subClassName);
1431 37
                        $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
1432
1433 37
                        foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1434 37
                            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...
1435 37
                                continue;
1436
                            }
1437
1438 33
                            $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1439 33
                            $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
1440
1441 33
                            $col = $sqlTableAlias . '.' . $quotedColumnName;
1442
1443 33
                            if (isset($mapping['requireSQLConversion'])) {
1444
                                $type = Type::getType($mapping['type']);
1445
                                $col = $type->convertToPHPValueSQL($col, $this->platform);
1446
                            }
1447
1448 33
                            $sqlParts[] = $col . ' AS ' . $columnAlias;
1449
1450 33
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1451
1452 37
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1453
                        }
1454
                    }
1455
                }
1456
1457 477
                $sql .= implode(', ', $sqlParts);
1458
        }
1459
1460 613
        return $sql;
1461
    }
1462
1463
    /**
1464
     * {@inheritdoc}
1465
     */
1466
    public function walkQuantifiedExpression($qExpr)
1467
    {
1468
        return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1469
    }
1470
1471
    /**
1472
     * {@inheritdoc}
1473
     */
1474 33
    public function walkSubselect($subselect)
1475
    {
1476 33
        $useAliasesBefore  = $this->useSqlTableAliases;
1477 33
        $rootAliasesBefore = $this->rootAliases;
1478
1479 33
        $this->rootAliases = array(); // reset the rootAliases for the subselect
1480 33
        $this->useSqlTableAliases = true;
1481
1482 33
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1483 33
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1484 33
        $sql .= $this->walkWhereClause($subselect->whereClause);
0 ignored issues
show
Bug introduced by
It seems like $subselect->whereClause can be null; however, walkWhereClause() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1485
1486 33
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1487 33
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1488 33
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1489
1490 33
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1491 33
        $this->useSqlTableAliases = $useAliasesBefore;
1492
1493 33
        return $sql;
1494
    }
1495
1496
    /**
1497
     * {@inheritdoc}
1498
     */
1499 33
    public function walkSubselectFromClause($subselectFromClause)
1500
    {
1501 33
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1502 33
        $sqlParts               = array ();
1503
1504 33
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1505 33
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1506
        }
1507
1508 33
        return ' FROM ' . implode(', ', $sqlParts);
1509
    }
1510
1511
    /**
1512
     * {@inheritdoc}
1513
     */
1514 33
    public function walkSimpleSelectClause($simpleSelectClause)
1515
    {
1516 33
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1517 33
            . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1518
    }
1519
1520
    /**
1521
     * @param \Doctrine\ORM\Query\AST\ParenthesisExpression $parenthesisExpression
1522
     *
1523
     * @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...
1524
     */
1525 21
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1526
    {
1527 21
        return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
1528
    }
1529
1530
    /**
1531
     * @param AST\NewObjectExpression $newObjectExpression
1532
     *
1533
     * @return string The SQL.
1534
     */
1535 22
    public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
1536
    {
1537 22
        $sqlSelectExpressions = array();
1538 22
        $objIndex             = $newObjectResultAlias?:$this->newObjectCounter++;
1539
1540 22
        foreach ($newObjectExpression->args as $argIndex => $e) {
1541 22
            $resultAlias = $this->scalarResultCounter++;
1542 22
            $columnAlias = $this->getSQLColumnAlias('sclr');
1543 22
            $fieldType   = 'string';
1544
1545
            switch (true) {
1546 22
                case ($e instanceof AST\NewObjectExpression):
1547
                    $sqlSelectExpressions[] = $e->dispatch($this);
1548
                    break;
1549
1550
                case ($e instanceof AST\Subselect):
1551 1
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1552 1
                    break;
1553
1554
                case ($e instanceof AST\PathExpression):
1555 22
                    $dqlAlias  = $e->identificationVariable;
1556 22
                    $qComp     = $this->queryComponents[$dqlAlias];
1557 22
                    $class     = $qComp['metadata'];
1558 22
                    $fieldType = $class->fieldMappings[$e->field]['type'];
1559
1560 22
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1561 22
                    break;
1562
1563 5
                case ($e instanceof AST\Literal):
1564 1
                    switch ($e->type) {
1565 1
                        case AST\Literal::BOOLEAN:
1566 1
                            $fieldType = 'boolean';
1567 1
                            break;
1568
1569 1
                        case AST\Literal::NUMERIC:
1570 1
                            $fieldType = is_float($e->value) ? 'float' : 'integer';
1571 1
                            break;
1572
                    }
1573
1574 1
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1575 1
                    break;
1576
1577
                default:
1578 5
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1579 5
                    break;
1580
            }
1581
1582 22
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1583 22
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1584
1585 22
            $this->rsm->newObjectMappings[$columnAlias] = array(
1586 22
                'className' => $newObjectExpression->className,
1587 22
                'objIndex'  => $objIndex,
1588 22
                'argIndex'  => $argIndex
1589
            );
1590
        }
1591
1592 22
        return implode(', ', $sqlSelectExpressions);
1593
    }
1594
1595
    /**
1596
     * {@inheritdoc}
1597
     */
1598 33
    public function walkSimpleSelectExpression($simpleSelectExpression)
1599
    {
1600 33
        $expr = $simpleSelectExpression->expression;
1601 33
        $sql  = ' ';
1602
1603
        switch (true) {
1604 33
            case ($expr instanceof AST\PathExpression):
1605 9
                $sql .= $this->walkPathExpression($expr);
1606 9
                break;
1607
1608
            case ($expr instanceof AST\AggregateExpression):
1609 14
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1610
1611 14
                $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
1612 14
                break;
1613
1614
            case ($expr instanceof AST\Subselect):
1615
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1616
1617
                $columnAlias = 'sclr' . $this->aliasCounter++;
1618
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1619
1620
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1621
                break;
1622
1623
            case ($expr instanceof AST\Functions\FunctionNode):
1624
            case ($expr instanceof AST\SimpleArithmeticExpression):
1625
            case ($expr instanceof AST\ArithmeticTerm):
1626 7
            case ($expr instanceof AST\ArithmeticFactor):
1627
            case ($expr instanceof AST\Literal):
1628
            case ($expr instanceof AST\NullIfExpression):
1629
            case ($expr instanceof AST\CoalesceExpression):
1630
            case ($expr instanceof AST\GeneralCaseExpression):
1631
            case ($expr instanceof AST\SimpleCaseExpression):
1632 8
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1633
1634 8
                $columnAlias = $this->getSQLColumnAlias('sclr');
1635 8
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1636
1637 8
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1638 8
                break;
1639
1640 2
            case ($expr instanceof AST\ParenthesisExpression):
1641 1
                $sql .= $this->walkParenthesisExpression($expr);
1642 1
                break;
1643
1644
            default: // IdentificationVariable
1645 2
                $sql .= $this->walkEntityIdentificationVariable($expr);
1646 2
                break;
1647
        }
1648
1649 33
        return $sql;
1650
    }
1651
1652
    /**
1653
     * {@inheritdoc}
1654
     */
1655 75
    public function walkAggregateExpression($aggExpression)
1656
    {
1657 75
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1658 75
            . $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...
1659
    }
1660
1661
    /**
1662
     * {@inheritdoc}
1663
     */
1664 23
    public function walkGroupByClause($groupByClause)
1665
    {
1666 23
        $sqlParts = array();
1667
1668 23
        foreach ($groupByClause->groupByItems as $groupByItem) {
1669 23
            $sqlParts[] = $this->walkGroupByItem($groupByItem);
1670
        }
1671
1672 23
        return ' GROUP BY ' . implode(', ', $sqlParts);
1673
    }
1674
1675
    /**
1676
     * {@inheritdoc}
1677
     */
1678 23
    public function walkGroupByItem($groupByItem)
1679
    {
1680
        // StateFieldPathExpression
1681 23
        if ( ! is_string($groupByItem)) {
1682 10
            return $this->walkPathExpression($groupByItem);
1683
        }
1684
1685
        // ResultVariable
1686 13
        if (isset($this->queryComponents[$groupByItem]['resultVariable'])) {
1687 2
            $resultVariable = $this->queryComponents[$groupByItem]['resultVariable'];
1688
1689 2
            if ($resultVariable instanceof AST\PathExpression) {
1690 1
                return $this->walkPathExpression($resultVariable);
1691
            }
1692
1693 1
            if (isset($resultVariable->pathExpression)) {
1694
                return $this->walkPathExpression($resultVariable->pathExpression);
1695
            }
1696
1697 1
            return $this->walkResultVariable($groupByItem);
1698
        }
1699
1700
        // IdentificationVariable
1701 11
        $sqlParts = array();
1702
1703 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->fieldNames as $field) {
1704 11
            $item       = new AST\PathExpression(AST\PathExpression::TYPE_STATE_FIELD, $groupByItem, $field);
1705 11
            $item->type = AST\PathExpression::TYPE_STATE_FIELD;
1706
1707 11
            $sqlParts[] = $this->walkPathExpression($item);
1708
        }
1709
1710 11
        foreach ($this->queryComponents[$groupByItem]['metadata']->associationMappings as $mapping) {
1711 11
            if ($mapping['isOwningSide'] && $mapping['type'] & ClassMetadataInfo::TO_ONE) {
1712 7
                $item       = new AST\PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $groupByItem, $mapping['fieldName']);
1713 7
                $item->type = AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION;
1714
1715 11
                $sqlParts[] = $this->walkPathExpression($item);
1716
            }
1717
        }
1718
1719 11
        return implode(', ', $sqlParts);
1720
    }
1721
1722
    /**
1723
     * {@inheritdoc}
1724
     */
1725 37
    public function walkDeleteClause(AST\DeleteClause $deleteClause)
1726
    {
1727 37
        $class     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1728 37
        $tableName = $class->getTableName();
1729 37
        $sql       = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform);
1730
1731 37
        $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1732 37
        $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1733
1734 37
        return $sql;
1735
    }
1736
1737
    /**
1738
     * {@inheritdoc}
1739
     */
1740 25
    public function walkUpdateClause($updateClause)
1741
    {
1742 25
        $class     = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1743 25
        $tableName = $class->getTableName();
1744 25
        $sql       = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform);
1745
1746 25
        $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1747 25
        $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1748
1749 25
        $sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems));
1750
1751 25
        return $sql;
1752
    }
1753
1754
    /**
1755
     * {@inheritdoc}
1756
     */
1757 29
    public function walkUpdateItem($updateItem)
1758
    {
1759 29
        $useTableAliasesBefore = $this->useSqlTableAliases;
1760 29
        $this->useSqlTableAliases = false;
1761
1762 29
        $sql      = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1763 29
        $newValue = $updateItem->newValue;
1764
1765
        switch (true) {
1766 29
            case ($newValue instanceof AST\Node):
1767 28
                $sql .= $newValue->dispatch($this);
1768 28
                break;
1769
1770 1
            case ($newValue === null):
1771 1
                $sql .= 'NULL';
1772 1
                break;
1773
1774
            default:
1775
                $sql .= $this->conn->quote($newValue);
1776
                break;
1777
        }
1778
1779 29
        $this->useSqlTableAliases = $useTableAliasesBefore;
1780
1781 29
        return $sql;
1782
    }
1783
1784
    /**
1785
     * {@inheritdoc}
1786
     */
1787 670
    public function walkWhereClause($whereClause)
1788
    {
1789 670
        $condSql  = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1790 667
        $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->rootAliases);
1791
1792 667
        if ($this->em->hasFilters()) {
1793 43
            $filterClauses = array();
1794 43
            foreach ($this->rootAliases as $dqlAlias) {
1795 43
                $class = $this->queryComponents[$dqlAlias]['metadata'];
1796 43
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1797
1798 43
                if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) {
1799 43
                    $filterClauses[] = $filterExpr;
1800
                }
1801
            }
1802
1803 43
            if (count($filterClauses)) {
1804 6
                if ($condSql) {
1805 2
                    $condSql = '(' . $condSql . ') AND ';
1806
                }
1807
1808 6
                $condSql .= implode(' AND ', $filterClauses);
1809
            }
1810
        }
1811
1812 667
        if ($condSql) {
1813 327
            return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1814
        }
1815
1816 415
        if ($discrSql) {
1817 21
            return ' WHERE ' . $discrSql;
1818
        }
1819
1820 396
        return '';
1821
    }
1822
1823
    /**
1824
     * {@inheritdoc}
1825
     */
1826 358
    public function walkConditionalExpression($condExpr)
1827
    {
1828
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1829
        // if only one ConditionalTerm is defined
1830 358
        if ( ! ($condExpr instanceof AST\ConditionalExpression)) {
1831 303
            return $this->walkConditionalTerm($condExpr);
1832
        }
1833
1834 72
        return implode(' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms));
1835
    }
1836
1837
    /**
1838
     * {@inheritdoc}
1839
     */
1840 358
    public function walkConditionalTerm($condTerm)
1841
    {
1842
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1843
        // if only one ConditionalFactor is defined
1844 358
        if ( ! ($condTerm instanceof AST\ConditionalTerm)) {
1845 291
            return $this->walkConditionalFactor($condTerm);
1846
        }
1847
1848 90
        return implode(' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors));
1849
    }
1850
1851
    /**
1852
     * {@inheritdoc}
1853
     */
1854 358
    public function walkConditionalFactor($factor)
1855
    {
1856
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1857
        // if only one ConditionalPrimary is defined
1858 358
        return ( ! ($factor instanceof AST\ConditionalFactor))
1859 355
            ? $this->walkConditionalPrimary($factor)
1860 355
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1861
    }
1862
1863
    /**
1864
     * {@inheritdoc}
1865
     */
1866 358
    public function walkConditionalPrimary($primary)
1867
    {
1868 358
        if ($primary->isSimpleConditionalExpression()) {
1869 358
            return $primary->simpleConditionalExpression->dispatch($this);
1870
        }
1871
1872 25
        if ($primary->isConditionalExpression()) {
1873 25
            $condExpr = $primary->conditionalExpression;
1874
1875 25
            return '(' . $this->walkConditionalExpression($condExpr) . ')';
0 ignored issues
show
Bug introduced by
It seems like $condExpr defined by $primary->conditionalExpression on line 1873 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...
1876
        }
1877
    }
1878
1879
    /**
1880
     * {@inheritdoc}
1881
     */
1882 5
    public function walkExistsExpression($existsExpr)
1883
    {
1884 5
        $sql = ($existsExpr->not) ? 'NOT ' : '';
1885
1886 5
        $sql .= 'EXISTS (' . $this->walkSubselect($existsExpr->subselect) . ')';
1887
1888 5
        return $sql;
1889
    }
1890
1891
    /**
1892
     * {@inheritdoc}
1893
     */
1894 6
    public function walkCollectionMemberExpression($collMemberExpr)
1895
    {
1896 6
        $sql = $collMemberExpr->not ? 'NOT ' : '';
1897 6
        $sql .= 'EXISTS (SELECT 1 FROM ';
1898
1899 6
        $entityExpr   = $collMemberExpr->entityExpression;
1900 6
        $collPathExpr = $collMemberExpr->collectionValuedPathExpression;
1901
1902 6
        $fieldName = $collPathExpr->field;
1903 6
        $dqlAlias  = $collPathExpr->identificationVariable;
1904
1905 6
        $class = $this->queryComponents[$dqlAlias]['metadata'];
1906
1907
        switch (true) {
1908
            // InputParameter
1909 6
            case ($entityExpr instanceof AST\InputParameter):
1910 4
                $dqlParamKey = $entityExpr->name;
1911 4
                $entitySql   = '?';
1912 4
                break;
1913
1914
            // SingleValuedAssociationPathExpression | IdentificationVariable
1915
            case ($entityExpr instanceof AST\PathExpression):
1916 2
                $entitySql = $this->walkPathExpression($entityExpr);
1917 2
                break;
1918
1919
            default:
1920
                throw new \BadMethodCallException("Not implemented");
1921
        }
1922
1923 6
        $assoc = $class->associationMappings[$fieldName];
1924
1925 6
        if ($assoc['type'] == ClassMetadata::ONE_TO_MANY) {
1926 1
            $targetClass      = $this->em->getClassMetadata($assoc['targetEntity']);
1927 1
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1928 1
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1929
1930 1
            $sql .= $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' WHERE ';
1931
1932 1
            $owningAssoc = $targetClass->associationMappings[$assoc['mappedBy']];
1933 1
            $sqlParts    = array();
1934
1935 1
            foreach ($owningAssoc['targetToSourceKeyColumns'] as $targetColumn => $sourceColumn) {
1936 1
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$targetColumn], $class, $this->platform);
1937
1938 1
                $sqlParts[] = $sourceTableAlias . '.' . $targetColumn . ' = ' . $targetTableAlias . '.' . $sourceColumn;
1939
            }
1940
1941 1
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1942 1
                if (isset($dqlParamKey)) {
1943 1
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1944
                }
1945
1946 1
                $sqlParts[] = $targetTableAlias . '.'  . $targetColumnName . ' = ' . $entitySql;
1947
            }
1948
1949 1
            $sql .= implode(' AND ', $sqlParts);
1950
        } else { // many-to-many
1951 5
            $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
1952
1953 5
            $owningAssoc = $assoc['isOwningSide'] ? $assoc : $targetClass->associationMappings[$assoc['mappedBy']];
1954 5
            $joinTable = $owningAssoc['joinTable'];
1955
1956
            // SQL table aliases
1957 5
            $joinTableAlias   = $this->getSQLTableAlias($joinTable['name']);
1958 5
            $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName());
1959 5
            $sourceTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
1960
1961
            // join to target table
1962 5
            $sql .= $this->quoteStrategy->getJoinTableName($owningAssoc, $targetClass, $this->platform) . ' ' . $joinTableAlias
1963 5
                . ' INNER JOIN ' . $this->quoteStrategy->getTableName($targetClass, $this->platform) . ' ' . $targetTableAlias . ' ON ';
1964
1965
            // join conditions
1966 5
            $joinColumns  = $assoc['isOwningSide'] ? $joinTable['inverseJoinColumns'] : $joinTable['joinColumns'];
1967 5
            $joinSqlParts = array();
1968
1969 5
            foreach ($joinColumns as $joinColumn) {
1970 5
                $targetColumn = $this->quoteStrategy->getColumnName($targetClass->fieldNames[$joinColumn['referencedColumnName']], $targetClass, $this->platform);
1971
1972 5
                $joinSqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $targetTableAlias . '.' . $targetColumn;
1973
            }
1974
1975 5
            $sql .= implode(' AND ', $joinSqlParts);
1976 5
            $sql .= ' WHERE ';
1977
1978 5
            $joinColumns = $assoc['isOwningSide'] ? $joinTable['joinColumns'] : $joinTable['inverseJoinColumns'];
1979 5
            $sqlParts    = array();
1980
1981 5
            foreach ($joinColumns as $joinColumn) {
1982 5
                $targetColumn = $this->quoteStrategy->getColumnName($class->fieldNames[$joinColumn['referencedColumnName']], $class, $this->platform);
1983
1984 5
                $sqlParts[] = $joinTableAlias . '.' . $joinColumn['name'] . ' = ' . $sourceTableAlias . '.' . $targetColumn;
1985
            }
1986
1987 5
            foreach ($this->quoteStrategy->getIdentifierColumnNames($targetClass, $this->platform) as $targetColumnName) {
1988 5
                if (isset($dqlParamKey)) {
1989 3
                    $this->parserResult->addParameterMapping($dqlParamKey, $this->sqlParamIndex++);
1990
                }
1991
1992 5
                $sqlParts[] = $targetTableAlias . '.' . $targetColumnName . ' IN (' . $entitySql . ')';
1993
            }
1994
1995 5
            $sql .= implode(' AND ', $sqlParts);
1996
        }
1997
1998 6
        return $sql . ')';
1999
    }
2000
2001
    /**
2002
     * {@inheritdoc}
2003
     */
2004 3
    public function walkEmptyCollectionComparisonExpression($emptyCollCompExpr)
2005
    {
2006 3
        $sizeFunc = new AST\Functions\SizeFunction('size');
2007 3
        $sizeFunc->collectionPathExpression = $emptyCollCompExpr->expression;
2008
2009 3
        return $sizeFunc->getSql($this) . ($emptyCollCompExpr->not ? ' > 0' : ' = 0');
2010
    }
2011
2012
    /**
2013
     * {@inheritdoc}
2014
     */
2015 10
    public function walkNullComparisonExpression($nullCompExpr)
2016
    {
2017 10
        $expression = $nullCompExpr->expression;
2018 10
        $comparison = ' IS' . ($nullCompExpr->not ? ' NOT' : '') . ' NULL';
2019
2020
        // Handle ResultVariable
2021 10
        if (is_string($expression) && isset($this->queryComponents[$expression]['resultVariable'])) {
2022 2
            return $this->walkResultVariable($expression) . $comparison;
2023
        }
2024
2025
        // Handle InputParameter mapping inclusion to ParserResult
2026 8
        if ($expression instanceof AST\InputParameter) {
2027
            return $this->walkInputParameter($expression) . $comparison;
2028
        }
2029
2030 8
        return $expression->dispatch($this) . $comparison;
2031
    }
2032
2033
    /**
2034
     * {@inheritdoc}
2035
     */
2036 85
    public function walkInExpression($inExpr)
2037
    {
2038 85
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2039
2040 84
        $sql .= ($inExpr->subselect)
2041 7
            ? $this->walkSubselect($inExpr->subselect)
2042 84
            : implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals));
2043
2044 84
        $sql .= ')';
2045
2046 84
        return $sql;
2047
    }
2048
2049
    /**
2050
     * {@inheritdoc}
2051
     */
2052 9
    public function walkInstanceOfExpression($instanceOfExpr)
2053
    {
2054 9
        $sql = '';
2055
2056 9
        $dqlAlias = $instanceOfExpr->identificationVariable;
2057 9
        $discrClass = $class = $this->queryComponents[$dqlAlias]['metadata'];
2058
2059 9
        if ($class->discriminatorColumn) {
2060 9
            $discrClass = $this->em->getClassMetadata($class->rootEntityName);
2061
        }
2062
2063 9
        if ($this->useSqlTableAliases) {
2064 9
            $sql .= $this->getSQLTableAlias($discrClass->getTableName(), $dqlAlias) . '.';
2065
        }
2066
2067 9
        $sql .= $class->discriminatorColumn['name'] . ($instanceOfExpr->not ? ' NOT IN ' : ' IN ');
2068
2069 9
        $sqlParameterList = array();
2070
2071 9
        foreach ($instanceOfExpr->value as $parameter) {
2072 9
            if ($parameter instanceof AST\InputParameter) {
2073 3
                $this->rsm->addMetadataParameterMapping($parameter->name, 'discriminatorValue');
2074
2075 3
                $sqlParameterList[] = $this->walkInputParameter($parameter);
2076
2077 3
                continue;
2078
            }
2079
2080
            // Get name from ClassMetadata to resolve aliases.
2081 6
            $entityClassName    = $this->em->getClassMetadata($parameter)->name;
2082 6
            $discriminatorValue = $class->discriminatorValue;
2083
2084 6
            if ($entityClassName !== $class->name) {
2085 5
                $discrMap = array_flip($class->discriminatorMap);
2086
2087 5
                if ( ! isset($discrMap[$entityClassName])) {
2088 1
                    throw QueryException::instanceOfUnrelatedClass($entityClassName, $class->rootEntityName);
2089
                }
2090
2091 4
                $discriminatorValue = $discrMap[$entityClassName];
2092
            }
2093
2094 5
            $sqlParameterList[] = $this->conn->quote($discriminatorValue);
2095
        }
2096
2097 8
        $sql .= '(' . implode(', ', $sqlParameterList) . ')';
2098
2099 8
        return $sql;
2100
    }
2101
2102
    /**
2103
     * {@inheritdoc}
2104
     */
2105 77
    public function walkInParameter($inParam)
2106
    {
2107 77
        return $inParam instanceof AST\InputParameter
2108 68
            ? $this->walkInputParameter($inParam)
2109 77
            : $this->walkLiteral($inParam);
2110
    }
2111
2112
    /**
2113
     * {@inheritdoc}
2114
     */
2115 148
    public function walkLiteral($literal)
2116
    {
2117 148
        switch ($literal->type) {
2118 148
            case AST\Literal::STRING:
2119 51
                return $this->conn->quote($literal->value);
2120
2121 111
            case AST\Literal::BOOLEAN:
2122 8
                $bool = strtolower($literal->value) == 'true' ? true : false;
2123 8
                $boolVal = $this->conn->getDatabasePlatform()->convertBooleans($bool);
2124
2125 8
                return $boolVal;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $boolVal; (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...
2126
2127 104
            case AST\Literal::NUMERIC:
2128 104
                return $literal->value;
2129
2130
            default:
2131
                throw QueryException::invalidLiteral($literal);
2132
        }
2133
    }
2134
2135
    /**
2136
     * {@inheritdoc}
2137
     */
2138 6
    public function walkBetweenExpression($betweenExpr)
2139
    {
2140 6
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2141
2142 6
        if ($betweenExpr->not) {
2143 2
            $sql .= ' NOT';
2144
        }
2145
2146 6
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2147 6
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2148
2149 6
        return $sql;
2150
    }
2151
2152
    /**
2153
     * {@inheritdoc}
2154
     */
2155 9
    public function walkLikeExpression($likeExpr)
2156
    {
2157 9
        $stringExpr = $likeExpr->stringExpression;
2158 9
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2159 1
            ? $this->walkResultVariable($stringExpr)
2160 9
            : $stringExpr->dispatch($this);
2161
2162 9
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2163
2164 9
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2165 4
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2166 6
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2167 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2168 6
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2169 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2170
        } else {
2171 6
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2172
        }
2173
2174 9
        if ($likeExpr->escapeChar) {
2175 1
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2176
        }
2177
2178 9
        return $sql;
2179
    }
2180
2181
    /**
2182
     * {@inheritdoc}
2183
     */
2184 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2185
    {
2186 5
        return $this->walkPathExpression($stateFieldPathExpression);
2187
    }
2188
2189
    /**
2190
     * {@inheritdoc}
2191
     */
2192 256
    public function walkComparisonExpression($compExpr)
2193
    {
2194 256
        $leftExpr  = $compExpr->leftExpression;
2195 256
        $rightExpr = $compExpr->rightExpression;
2196 256
        $sql       = '';
2197
2198 256
        $sql .= ($leftExpr instanceof AST\Node)
2199 256
            ? $leftExpr->dispatch($this)
2200 255
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2201
2202 255
        $sql .= ' ' . $compExpr->operator . ' ';
2203
2204 255
        $sql .= ($rightExpr instanceof AST\Node)
2205 253
            ? $rightExpr->dispatch($this)
2206 255
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2207
2208 255
        return $sql;
2209
    }
2210
2211
    /**
2212
     * {@inheritdoc}
2213
     */
2214 210
    public function walkInputParameter($inputParam)
2215
    {
2216 210
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2217
2218 210
        $parameter = $this->query->getParameter($inputParam->name);
2219
2220 210
        if ($parameter && Type::hasType($type = $parameter->getType())) {
2221 52
            return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2222
        }
2223
2224 164
        return '?';
2225
    }
2226
2227
    /**
2228
     * {@inheritdoc}
2229
     */
2230 322
    public function walkArithmeticExpression($arithmeticExpr)
2231
    {
2232 322
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2233 322
            ? $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...
2234 320
            : '(' . $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...
2235
    }
2236
2237
    /**
2238
     * {@inheritdoc}
2239
     */
2240 384
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2241
    {
2242 384
        if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2243 334
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2244
        }
2245
2246 75
        return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms));
2247
    }
2248
2249
    /**
2250
     * {@inheritdoc}
2251
     */
2252 405
    public function walkArithmeticTerm($term)
2253
    {
2254 405
        if (is_string($term)) {
2255 21
            return (isset($this->queryComponents[$term]))
2256 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2257 21
                : $term;
2258
        }
2259
2260
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2261
        // if only one ArithmeticFactor is defined
2262 404
        if ( ! ($term instanceof AST\ArithmeticTerm)) {
2263 382
            return $this->walkArithmeticFactor($term);
2264
        }
2265
2266 47
        return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors));
2267
    }
2268
2269
    /**
2270
     * {@inheritdoc}
2271
     */
2272 404
    public function walkArithmeticFactor($factor)
2273
    {
2274 404
        if (is_string($factor)) {
2275 47
            return (isset($this->queryComponents[$factor]))
2276 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2277 47
                : $factor;
2278
        }
2279
2280
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2281
        // if only one ArithmeticPrimary is defined
2282 404
        if ( ! ($factor instanceof AST\ArithmeticFactor)) {
2283 404
            return $this->walkArithmeticPrimary($factor);
2284
        }
2285
2286
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2287
2288
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2289
    }
2290
2291
    /**
2292
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2293
     *
2294
     * @param mixed $primary
2295
     *
2296
     * @return string The SQL.
2297
     */
2298 404
    public function walkArithmeticPrimary($primary)
2299
    {
2300 404
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2301
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2302
        }
2303
2304 404
        if ($primary instanceof AST\Node) {
2305 404
            return $primary->dispatch($this);
2306
        }
2307
2308
        return $this->walkEntityIdentificationVariable($primary);
2309
    }
2310
2311
    /**
2312
     * {@inheritdoc}
2313
     */
2314 18
    public function walkStringPrimary($stringPrimary)
2315
    {
2316 18
        return (is_string($stringPrimary))
2317
            ? $this->conn->quote($stringPrimary)
2318 18
            : $stringPrimary->dispatch($this);
2319
    }
2320
2321
    /**
2322
     * {@inheritdoc}
2323
     */
2324 30
    public function walkResultVariable($resultVariable)
2325
    {
2326 30
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2327
2328 30
        if (is_array($resultAlias)) {
2329 1
            return implode(', ', $resultAlias);
2330
        }
2331
2332 29
        return $resultAlias;
2333
    }
2334
}
2335