Completed
Pull Request — master (#5987)
by Javier
09:33
created

SqlWalker::walkLiteral()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 0
Metric Value
dl 0
loc 18
ccs 9
cts 10
cp 0.9
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 1
crap 4.016
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 681
    public function __construct($query, $parserResult, array $queryComponents)
182
    {
183 681
        $this->query            = $query;
184 681
        $this->parserResult     = $parserResult;
185 681
        $this->queryComponents  = $queryComponents;
186 681
        $this->rsm              = $parserResult->getResultSetMapping();
187 681
        $this->em               = $query->getEntityManager();
188 681
        $this->conn             = $this->em->getConnection();
189 681
        $this->platform         = $this->conn->getDatabasePlatform();
190 681
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
191 681
    }
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 675
    public function getExecutor($AST)
261
    {
262
        switch (true) {
263 675
            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 615
            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 615
                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 627
    public function getSQLTableAlias($tableName, $dqlAlias = '')
291
    {
292 627
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
293
294 627
        if ( ! isset($this->tableAliasMap[$tableName])) {
295 627
            $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
296 627
                . $this->tableAliasCounter++ . '_';
297
        }
298
299 627
        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 615
    public function getSQLColumnAlias($columnName)
329
    {
330 615
        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 608
    private function _generateOrderedCollectionOrderByItems()
399
    {
400 608
        $orderedColumns = array();
401
402 608
        foreach ($this->selectedClasses as $selectedClass) {
403 472
            $dqlAlias  = $selectedClass['dqlAlias'];
404 472
            $qComp     = $this->queryComponents[$dqlAlias];
405
406 472
            if ( ! isset($qComp['relation']['orderBy'])) {
407 472
                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 608
        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 669
    private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
441
    {
442 669
        $sqlParts = array();
443
444 669
        foreach ($dqlAliases as $dqlAlias) {
445 669
            $class = $this->queryComponents[$dqlAlias]['metadata'];
446
447 669
            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 669
        $sql = implode(' AND ', $sqlParts);
468
469 669
        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 318
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
481
    {
482 318
        if (!$this->em->hasFilters()) {
483 280
            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 615
    public function walkSelectStatement(AST\SelectStatement $AST)
521
    {
522 615
        $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 615
        $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 615
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
525 615
        $sql      = $this->walkSelectClause($AST->selectClause)
526 615
            . $this->walkFromClause($AST->fromClause)
527 612
            . $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 609
        if ($AST->groupByClause) {
530 23
            $sql .= $this->walkGroupByClause($AST->groupByClause);
531
        }
532
533 609
        if ($AST->havingClause) {
534 14
            $sql .= $this->walkHavingClause($AST->havingClause);
535
        }
536
537 609
        if ($AST->orderByClause) {
538 142
            $sql .= $this->walkOrderByClause($AST->orderByClause);
539
        }
540
541 608
        if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) {
542 6
            $sql .= ' ORDER BY ' . $orderBySql;
543
        }
544
545 608
        if ($limit !== null || $offset !== null) {
546 38
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
547
        }
548
549 608
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
550 603
            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 417
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
628
    {
629 417
        $class = $this->queryComponents[$identificationVariable]['metadata'];
630
631
        if (
632 417
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
633 417
            isset($class->fieldMappings[$fieldName]['inherited'])
634
        ) {
635 37
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
636
        }
637
638 417
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
639
    }
640
641
    /**
642
     * {@inheritdoc}
643
     */
644 486
    public function walkPathExpression($pathExpr)
645
    {
646 486
        $sql = '';
647
648 486
        switch ($pathExpr->type) {
649 486
            case AST\PathExpression::TYPE_STATE_FIELD:
650 465
                $fieldName = $pathExpr->field;
651 465
                $dqlAlias = $pathExpr->identificationVariable;
652 465
                $class = $this->queryComponents[$dqlAlias]['metadata'];
653
654 465
                if ($this->useSqlTableAliases) {
655 417
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
656
                }
657
658 465
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
659 465
                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 483
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 615
    public function walkSelectClause($selectClause)
701
    {
702 615
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
703 615
        $sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions));
704
705 615
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
706 1
            $this->query->setHint(self::HINT_DISTINCT, true);
707
        }
708
709 615
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
710 452
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
711
            ||
712 288
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
713 615
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
714
715 615
        foreach ($this->selectedClasses as $selectedClass) {
716 479
            $class       = $selectedClass['class'];
717 479
            $dqlAlias    = $selectedClass['dqlAlias'];
718 479
            $resultAlias = $selectedClass['resultAlias'];
719
720
            // Register as entity or joined entity result
721 479
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
722 479
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
723
            } else {
724 158
                $this->rsm->addJoinedEntityResult(
725 158
                    $class->name,
726
                    $dqlAlias,
727 158
                    $this->queryComponents[$dqlAlias]['parent'],
728 158
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
729
                );
730
            }
731
732 479
            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 479
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
747 180
                continue;
748
            }
749
750
            // Add foreign key columns of class and also parent classes
751 348
            foreach ($class->associationMappings as $assoc) {
752 311
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
753 264
                    continue;
754 279
                } else if ( !$addMetaColumns && !isset($assoc['id'])) {
755
                    continue;
756
                }
757
758 279
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
759 279
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
760 279
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
761 279
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
762
763 279
                foreach ($assoc['joinColumns'] as $joinColumn) {
764 279
                    $columnName  = $joinColumn['name'];
765 279
                    $columnAlias = $this->getSQLColumnAlias($columnName);
766 279
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
767
768 279
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
769
770 279
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
771
                }
772
            }
773
774
            // Add foreign key columns to SQL, if necessary
775 348
            if ( ! $addMetaColumns) {
776 8
                continue;
777
            }
778
779
            // Add foreign key columns of subclasses
780 343
            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 343
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
799
                        }
800
                    }
801
                }
802
            }
803
        }
804
805 615
        $sql .= implode(', ', $sqlSelectExpressions);
806
807 615
        return $sql;
808
    }
809
810
    /**
811
     * {@inheritdoc}
812
     */
813 617
    public function walkFromClause($fromClause)
814
    {
815 617
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
816 617
        $sqlParts = array();
817
818 617
        foreach ($identificationVarDecls as $identificationVariableDecl) {
819 617
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
820
        }
821
822 614
        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 618
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
833
    {
834 618
        $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 618
        if ($identificationVariableDecl->indexBy) {
837 5
            $this->walkIndexBy($identificationVariableDecl->indexBy);
838
        }
839
840 618
        foreach ($identificationVariableDecl->joins as $join) {
841 244
            $sql .= $this->walkJoin($join);
842
        }
843
844 615
        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 618
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
877
    {
878 618
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
879 618
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
880
881 618
        if ($rangeVariableDeclaration->isRoot) {
882 618
            $this->rootAliases[] = $dqlAlias;
883
        }
884
885 618
        $sql = $this->platform->appendLockHint(
886 618
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
887 618
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
888 618
            $this->query->getHint(Query::HINT_LOCK_MODE)
889
        );
890
891 618
        if ($class->isInheritanceTypeJoined()) {
892 87
            $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
893
        }
894
895 618
        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 227
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
910
    {
911 227
        $sql = '';
912
913 227
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
914 227
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
915 227
        $indexBy                   = $joinAssociationDeclaration->indexBy;
916
917 227
        $relation        = $this->queryComponents[$joinedDqlAlias]['relation'];
918 227
        $targetClass     = $this->em->getClassMetadata($relation['targetEntity']);
919 227
        $sourceClass     = $this->em->getClassMetadata($relation['sourceEntity']);
920 227
        $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
921
922 227
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
923 227
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
924
925
        // Ensure we got the owning side, since it has all mapping info
926 227
        $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
927
928 227
        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 224
        $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 224
            case ($assoc['type'] & ClassMetadata::TO_ONE):
941 177
                $conditions = array();
942
943 177
                foreach ($assoc['joinColumns'] as $joinColumn) {
944 177
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
945 177
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
946
947 177
                    if ($relation['isOwningSide']) {
948 102
                        $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
949
950 102
                        continue;
951
                    }
952
953 108
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
954
                }
955
956
                // Apply remaining inheritance restrictions
957 177
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
958
959 177
                if ($discrSql) {
960 2
                    $conditions[] = $discrSql;
961
                }
962
963
                // Apply the filters
964 177
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
965
966 177
                if ($filterExpr) {
967 1
                    $conditions[] = $filterExpr;
968
                }
969
970
                $targetTableJoin = array(
971 177
                    'table' => $targetTableName . ' ' . $targetTableAlias,
972 177
                    'condition' => implode(' AND ', $conditions),
973
                );
974 177
                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 224
        $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1037
1038 224
        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 215
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1048
        }
1049
1050 224
        if ($withCondition) {
1051 5
            $sql .= ' AND ' . $withCondition;
1052
        }
1053
1054
        // Apply the indexes
1055 224
        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 219
        } else if (isset($relation['indexBy'])) {
1059 3
            $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
1060
        }
1061
1062 224
        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 244
    public function walkJoin($join)
1119
    {
1120 244
        $joinType        = $join->joinType;
1121 244
        $joinDeclaration = $join->joinAssociationDeclaration;
1122
1123 244
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1124 55
            ? ' LEFT JOIN '
1125 244
            : ' INNER JOIN ';
1126
1127
        switch (true) {
1128 244
            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 227
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1166 224
                break;
1167
        }
1168
1169 241
        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 615
    public function walkSelectExpression($selectExpression)
1287
    {
1288 615
        $sql    = '';
1289 615
        $expr   = $selectExpression->expression;
1290 615
        $hidden = $selectExpression->hiddenAliasResultVariable;
1291
1292
        switch (true) {
1293 615
            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 33
            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 106
                $columnAlias = $this->getSQLColumnAlias('sclr');
1342 106
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1343
1344 106
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1345
1346 106
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1347
1348 106
                if ( ! $hidden) {
1349
                    // We cannot resolve field type here; assume 'string'.
1350 106
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1351
                }
1352 106
                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 479
            case ($expr instanceof AST\NewObjectExpression):
1369 22
                $sql .= $this->walkNewObject($expr,$selectExpression->fieldIdentificationVariable);
1370 22
                break;
1371
1372
            default:
1373
                // IdentificationVariable or PartialObjectExpression
1374 479
                if ($expr instanceof AST\PartialObjectExpression) {
1375 16
                    $dqlAlias = $expr->identificationVariable;
1376 16
                    $partialFieldSet = $expr->partialFieldSet;
1377
                } else {
1378 474
                    $dqlAlias = $expr;
1379 474
                    $partialFieldSet = array();
1380
                }
1381
1382 479
                $queryComp   = $this->queryComponents[$dqlAlias];
1383 479
                $class       = $queryComp['metadata'];
1384 479
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1385
1386 479
                if ( ! isset($this->selectedClasses[$dqlAlias])) {
1387 479
                    $this->selectedClasses[$dqlAlias] = array(
1388 479
                        'class'       => $class,
1389 479
                        'dqlAlias'    => $dqlAlias,
1390 479
                        'resultAlias' => $resultAlias
1391
                    );
1392
                }
1393
1394 479
                $sqlParts = array();
1395
1396
                // Select all fields from the queried class
1397 479
                foreach ($class->fieldMappings as $fieldName => $mapping) {
1398 478
                    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 477
                    $tableName = (isset($mapping['inherited']))
1403 51
                        ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1404 477
                        : $class->getTableName();
1405
1406 477
                    $sqlTableAlias    = $this->getSQLTableAlias($tableName, $dqlAlias);
1407 477
                    $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1408 477
                    $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1409
1410 477
                    $col = $sqlTableAlias . '.' . $quotedColumnName;
1411
1412 477
                    if (isset($mapping['requireSQLConversion'])) {
1413 5
                        $type = Type::getType($mapping['type']);
1414 5
                        $col = $type->convertToPHPValueSQL($col, $this->platform);
1415
                    }
1416
1417 477
                    $sqlParts[] = $col . ' AS '. $columnAlias;
1418
1419 477
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1420
1421 477
                    $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 479
                if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1429 385
                    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 479
                $sql .= implode(', ', $sqlParts);
1458
        }
1459
1460 615
        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 22
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1526
    {
1527 22
        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
            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 672
    public function walkWhereClause($whereClause)
1788
    {
1789 672
        $condSql  = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1790 669
        $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->rootAliases);
1791
1792 669
        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 669
        if ($condSql) {
1813 328
            return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1814
        }
1815
1816 416
        if ($discrSql) {
1817 21
            return ' WHERE ' . $discrSql;
1818
        }
1819
1820 397
        return '';
1821
    }
1822
1823
    /**
1824
     * {@inheritdoc}
1825
     */
1826 359
    public function walkConditionalExpression($condExpr)
1827
    {
1828
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1829
        // if only one ConditionalTerm is defined
1830 359
        if ( ! ($condExpr instanceof AST\ConditionalExpression)) {
1831 304
            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 359
    public function walkConditionalTerm($condTerm)
1841
    {
1842
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1843
        // if only one ConditionalFactor is defined
1844 359
        if ( ! ($condTerm instanceof AST\ConditionalTerm)) {
1845 292
            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 359
    public function walkConditionalFactor($factor)
1855
    {
1856
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1857
        // if only one ConditionalPrimary is defined
1858 359
        return ( ! ($factor instanceof AST\ConditionalFactor))
1859 356
            ? $this->walkConditionalPrimary($factor)
1860 356
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1861
    }
1862
1863
    /**
1864
     * {@inheritdoc}
1865
     */
1866 359
    public function walkConditionalPrimary($primary)
1867
    {
1868 359
        if ($primary->isSimpleConditionalExpression()) {
1869 359
            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 149
    public function walkLiteral($literal)
2116
    {
2117 149
        switch ($literal->type) {
2118 149
            case AST\Literal::STRING:
2119 51
                return $this->conn->quote($literal->value);
2120
2121 112
            case AST\Literal::BOOLEAN:
2122 8
                $bool = 'true' === strtolower($literal->value);
2123
2124 8
                return $this->conn->getDatabasePlatform()->convertBooleans($bool);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->conn->getD...convertBooleans($bool); (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...
2125
2126 105
            case AST\Literal::NUMERIC:
2127 105
                return $literal->value;
2128
2129
            default:
2130
                throw QueryException::invalidLiteral($literal);
2131
        }
2132
    }
2133
2134
    /**
2135
     * {@inheritdoc}
2136
     */
2137 6
    public function walkBetweenExpression($betweenExpr)
2138
    {
2139 6
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2140
2141 6
        if ($betweenExpr->not) {
2142 2
            $sql .= ' NOT';
2143
        }
2144
2145 6
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2146 6
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2147
2148 6
        return $sql;
2149
    }
2150
2151
    /**
2152
     * {@inheritdoc}
2153
     */
2154 9
    public function walkLikeExpression($likeExpr)
2155
    {
2156 9
        $stringExpr = $likeExpr->stringExpression;
2157 9
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2158 1
            ? $this->walkResultVariable($stringExpr)
2159 9
            : $stringExpr->dispatch($this);
2160
2161 9
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2162
2163 9
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2164 4
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2165 6
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2166 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2167 6
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2168 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2169
        } else {
2170 6
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2171
        }
2172
2173 9
        if ($likeExpr->escapeChar) {
2174 1
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2175
        }
2176
2177 9
        return $sql;
2178
    }
2179
2180
    /**
2181
     * {@inheritdoc}
2182
     */
2183 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2184
    {
2185 5
        return $this->walkPathExpression($stateFieldPathExpression);
2186
    }
2187
2188
    /**
2189
     * {@inheritdoc}
2190
     */
2191 257
    public function walkComparisonExpression($compExpr)
2192
    {
2193 257
        $leftExpr  = $compExpr->leftExpression;
2194 257
        $rightExpr = $compExpr->rightExpression;
2195 257
        $sql       = '';
2196
2197 257
        $sql .= ($leftExpr instanceof AST\Node)
2198 257
            ? $leftExpr->dispatch($this)
2199 256
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2200
2201 256
        $sql .= ' ' . $compExpr->operator . ' ';
2202
2203 256
        $sql .= ($rightExpr instanceof AST\Node)
2204 254
            ? $rightExpr->dispatch($this)
2205 256
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2206
2207 256
        return $sql;
2208
    }
2209
2210
    /**
2211
     * {@inheritdoc}
2212
     */
2213 211
    public function walkInputParameter($inputParam)
2214
    {
2215 211
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2216
2217 211
        $parameter = $this->query->getParameter($inputParam->name);
2218
2219 211
        if ($parameter && Type::hasType($type = $parameter->getType())) {
2220 52
            return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2221
        }
2222
2223 165
        return '?';
2224
    }
2225
2226
    /**
2227
     * {@inheritdoc}
2228
     */
2229 323
    public function walkArithmeticExpression($arithmeticExpr)
2230
    {
2231 323
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2232 323
            ? $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...
2233 321
            : '(' . $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...
2234
    }
2235
2236
    /**
2237
     * {@inheritdoc}
2238
     */
2239 385
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2240
    {
2241 385
        if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2242 335
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2243
        }
2244
2245 75
        return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms));
2246
    }
2247
2248
    /**
2249
     * {@inheritdoc}
2250
     */
2251 406
    public function walkArithmeticTerm($term)
2252
    {
2253 406
        if (is_string($term)) {
2254 21
            return (isset($this->queryComponents[$term]))
2255 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2256 21
                : $term;
2257
        }
2258
2259
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2260
        // if only one ArithmeticFactor is defined
2261 405
        if ( ! ($term instanceof AST\ArithmeticTerm)) {
2262 383
            return $this->walkArithmeticFactor($term);
2263
        }
2264
2265 47
        return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors));
2266
    }
2267
2268
    /**
2269
     * {@inheritdoc}
2270
     */
2271 406
    public function walkArithmeticFactor($factor)
2272
    {
2273 406
        if (is_string($factor)) {
2274 47
            return (isset($this->queryComponents[$factor]))
2275 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2276 47
                : $factor;
2277
        }
2278
2279
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2280
        // if only one ArithmeticPrimary is defined
2281 406
        if ( ! ($factor instanceof AST\ArithmeticFactor)) {
2282 405
            return $this->walkArithmeticPrimary($factor);
2283
        }
2284
2285 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2286
2287 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2288
    }
2289
2290
    /**
2291
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2292
     *
2293
     * @param mixed $primary
2294
     *
2295
     * @return string The SQL.
2296
     */
2297 406
    public function walkArithmeticPrimary($primary)
2298
    {
2299 406
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2300
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2301
        }
2302
2303 406
        if ($primary instanceof AST\Node) {
2304 406
            return $primary->dispatch($this);
2305
        }
2306
2307
        return $this->walkEntityIdentificationVariable($primary);
2308
    }
2309
2310
    /**
2311
     * {@inheritdoc}
2312
     */
2313 18
    public function walkStringPrimary($stringPrimary)
2314
    {
2315 18
        return (is_string($stringPrimary))
2316
            ? $this->conn->quote($stringPrimary)
2317 18
            : $stringPrimary->dispatch($this);
2318
    }
2319
2320
    /**
2321
     * {@inheritdoc}
2322
     */
2323 30
    public function walkResultVariable($resultVariable)
2324
    {
2325 30
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2326
2327 30
        if (is_array($resultAlias)) {
2328 1
            return implode(', ', $resultAlias);
2329
        }
2330
2331 29
        return $resultAlias;
2332
    }
2333
}
2334