Completed
Pull Request — master (#6055)
by Martin
09:59
created

SqlWalker::walkSimpleCaseExpression()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 13
c 0
b 0
f 0
ccs 7
cts 7
cp 1
rs 9.4285
cc 2
eloc 7
nc 2
nop 1
crap 2
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Query;
21
22
use Doctrine\DBAL\LockMode;
23
use Doctrine\DBAL\Types\Type;
24
use Doctrine\ORM\Mapping\ClassMetadata;
25
use Doctrine\ORM\Mapping\ClassMetadataInfo;
26
use Doctrine\ORM\OptimisticLockException;
27
use Doctrine\ORM\Query;
28
use Doctrine\ORM\Utility\PersisterHelper;
29
30
/**
31
 * The SqlWalker is a TreeWalker that walks over a DQL AST and constructs
32
 * the corresponding SQL.
33
 *
34
 * @author Guilherme Blanco <[email protected]>
35
 * @author Roman Borschel <[email protected]>
36
 * @author Benjamin Eberlei <[email protected]>
37
 * @author Alexander <[email protected]>
38
 * @author Fabio B. Silva <[email protected]>
39
 * @since  2.0
40
 * @todo Rename: SQLWalker
41
 */
42
class SqlWalker implements TreeWalker
43
{
44
    /**
45
     * @var string
46
     */
47
    const HINT_DISTINCT = 'doctrine.distinct';
48
49
    /**
50
     * @var ResultSetMapping
51
     */
52
    private $rsm;
53
54
    /**
55
     * Counter for generating unique column aliases.
56
     *
57
     * @var integer
58
     */
59
    private $aliasCounter = 0;
60
61
    /**
62
     * Counter for generating unique table aliases.
63
     *
64
     * @var integer
65
     */
66
    private $tableAliasCounter = 0;
67
68
    /**
69
     * Counter for generating unique scalar result.
70
     *
71
     * @var integer
72
     */
73
    private $scalarResultCounter = 1;
74
75
    /**
76
     * Counter for generating unique parameter indexes.
77
     *
78
     * @var integer
79
     */
80
    private $sqlParamIndex = 0;
81
82
    /**
83
     * Counter for generating indexes.
84
     *
85
     * @var integer
86
     */
87
    private $newObjectCounter = 0;
88
89
    /**
90
     * @var ParserResult
91
     */
92
    private $parserResult;
93
94
    /**
95
     * @var \Doctrine\ORM\EntityManager
96
     */
97
    private $em;
98
99
    /**
100
     * @var \Doctrine\DBAL\Connection
101
     */
102
    private $conn;
103
104
    /**
105
     * @var \Doctrine\ORM\AbstractQuery
106
     */
107
    private $query;
108
109
    /**
110
     * @var array
111
     */
112
    private $tableAliasMap = 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 627
    public function __construct($query, $parserResult, array $queryComponents)
182
    {
183 627
        $this->query            = $query;
184 627
        $this->parserResult     = $parserResult;
185 627
        $this->queryComponents  = $queryComponents;
186 627
        $this->rsm              = $parserResult->getResultSetMapping();
187 627
        $this->em               = $query->getEntityManager();
188 627
        $this->conn             = $this->em->getConnection();
189 627
        $this->platform         = $this->conn->getDatabasePlatform();
190 627
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
191 627
    }
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 22
    public function getEntityManager()
219
    {
220 22
        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 621
    public function getExecutor($AST)
261
    {
262
        switch (true) {
263 621
            case ($AST instanceof AST\DeleteStatement):
264 35
                $primaryClass = $this->em->getClassMetadata($AST->deleteClause->abstractSchemaName);
265
266 35
                return ($primaryClass->isInheritanceTypeJoined())
267 1
                    ? new Exec\MultiTableDeleteExecutor($AST, $this)
268 35
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
269
270 563
            case ($AST instanceof AST\UpdateStatement):
271 26
                $primaryClass = $this->em->getClassMetadata($AST->updateClause->abstractSchemaName);
272
273 26
                return ($primaryClass->isInheritanceTypeJoined())
274 3
                    ? new Exec\MultiTableUpdateExecutor($AST, $this)
275 26
                    : new Exec\SingleTableDeleteUpdateExecutor($AST, $this);
276
277
            default:
278 563
                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 575
    public function getSQLTableAlias($tableName, $dqlAlias = '')
291
    {
292 575
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
293
294 575
        if ( ! isset($this->tableAliasMap[$tableName])) {
295 575
            $this->tableAliasMap[$tableName] = (preg_match('/[a-z]/i', $tableName[0]) ? strtolower($tableName[0]) : 't')
296 575
                . $this->tableAliasCounter++ . '_';
297
        }
298
299 575
        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 60
    public function setSQLTableAlias($tableName, $alias, $dqlAlias = '')
313
    {
314 60
        $tableName .= ($dqlAlias) ? '@[' . $dqlAlias . ']' : '';
315
316 60
        $this->tableAliasMap[$tableName] = $alias;
317
318 60
        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 563
    public function getSQLColumnAlias($columnName)
329
    {
330 563
        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 86
    private function _generateClassTableInheritanceJoins($class, $dqlAlias)
343
    {
344 86
        $sql = '';
345
346 86
        $baseTableAlias = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
347
348
        // INNER JOIN parent class tables
349 86
        foreach ($class->parentClasses as $parentClassName) {
350 62
            $parentClass = $this->em->getClassMetadata($parentClassName);
351 62
            $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 62
            $sql .= isset($this->queryComponents[$dqlAlias]['relation']) ? ' LEFT ' : ' INNER ';
355 62
            $sql .= 'JOIN ' . $this->quoteStrategy->getTableName($parentClass, $this->platform) . ' ' . $tableAlias . ' ON ';
356
357 62
            $sqlParts = array();
358
359 62
            foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
360 62
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
361
            }
362
363
            // Add filters on the root class
364 62
            if ($filterSql = $this->generateFilterConditionSQL($parentClass, $tableAlias)) {
365
                $sqlParts[] = $filterSql;
366
            }
367
368 62
            $sql .= implode(' AND ', $sqlParts);
369
        }
370
371
        // Ignore subclassing inclusion if partial objects is disallowed
372 86
        if ($this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
373 21
            return $sql;
374
        }
375
376
        // LEFT JOIN child class tables
377 65
        foreach ($class->subClasses as $subClassName) {
378 29
            $subClass   = $this->em->getClassMetadata($subClassName);
379 29
            $tableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
380
381 29
            $sql .= ' LEFT JOIN ' . $this->quoteStrategy->getTableName($subClass, $this->platform) . ' ' . $tableAlias . ' ON ';
382
383 29
            $sqlParts = array();
384
385 29
            foreach ($this->quoteStrategy->getIdentifierColumnNames($subClass, $this->platform) as $columnName) {
386 29
                $sqlParts[] = $baseTableAlias . '.' . $columnName . ' = ' . $tableAlias . '.' . $columnName;
387
            }
388
389 29
            $sql .= implode(' AND ', $sqlParts);
390
        }
391
392 65
        return $sql;
393
    }
394
395
    /**
396
     * @return string
397
     */
398 556
    private function _generateOrderedCollectionOrderByItems()
399
    {
400 556
        $orderedColumns = array();
401
402 556
        foreach ($this->selectedClasses as $selectedClass) {
403 409
            $dqlAlias  = $selectedClass['dqlAlias'];
404 409
            $qComp     = $this->queryComponents[$dqlAlias];
405
406 409
            if ( ! isset($qComp['relation']['orderBy'])) {
407 409
                continue;
408
            }
409
410 5
            $persister = $this->em->getUnitOfWork()->getEntityPersister($qComp['metadata']->name);
411
412 5
            foreach ($qComp['relation']['orderBy'] as $fieldName => $orientation) {
413 5
                $columnName = $this->quoteStrategy->getColumnName($fieldName, $qComp['metadata'], $this->platform);
414 5
                $tableName  = ($qComp['metadata']->isInheritanceTypeJoined())
415
                    ? $persister->getOwningTable($fieldName)
416 5
                    : $qComp['metadata']->getTableName();
417
418 5
                $orderedColumn = $this->getSQLTableAlias($tableName, $dqlAlias) . '.' . $columnName;
419
420
                // OrderByClause should replace an ordered relation. see - DDC-2475
421 5
                if (isset($this->orderedColumnsMap[$orderedColumn])) {
422 1
                    continue;
423
                }
424
425 5
                $this->orderedColumnsMap[$orderedColumn] = $orientation;
426 5
                $orderedColumns[] = $orderedColumn . ' ' . $orientation;
427
            }
428
        }
429
430 556
        return implode(', ', $orderedColumns);
431
    }
432
433
    /**
434
     * Generates a discriminator column SQL condition for the class with the given DQL alias.
435
     *
436
     * @param array $dqlAliases List of root DQL aliases to inspect for discriminator restrictions.
437
     *
438
     * @return string
439
     */
440 615
    private function _generateDiscriminatorColumnConditionSQL(array $dqlAliases)
441
    {
442 615
        $sqlParts = array();
443
444 615
        foreach ($dqlAliases as $dqlAlias) {
445 615
            $class = $this->queryComponents[$dqlAlias]['metadata'];
446
447 615
            if ( ! $class->isInheritanceTypeSingleTable()) continue;
448
449 33
            $conn   = $this->em->getConnection();
450 33
            $values = array();
451
452 33
            if ($class->discriminatorValue !== null) { // discriminators can be 0
453 15
                $values[] = $conn->quote($class->discriminatorValue);
454
            }
455
456 33
            foreach ($class->subClasses as $subclassName) {
457 24
                $values[] = $conn->quote($this->em->getClassMetadata($subclassName)->discriminatorValue);
458
            }
459
460 33
            $sqlTableAlias = ($this->useSqlTableAliases)
461 28
                ? $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.'
462 33
                : '';
463
464 33
            $sqlParts[] = $sqlTableAlias . $class->discriminatorColumn['name'] . ' IN (' . implode(', ', $values) . ')';
465
        }
466
467 615
        $sql = implode(' AND ', $sqlParts);
468
469 615
        return (count($sqlParts) > 1) ? '(' . $sql . ')' : $sql;
470
    }
471
472
    /**
473
     * Generates the filter SQL for a given entity and table alias.
474
     *
475
     * @param ClassMetadata $targetEntity     Metadata of the target entity.
476
     * @param string        $targetTableAlias The table alias of the joined/selected table.
477
     *
478
     * @return string The SQL query part to add to a query.
479
     */
480 267
    private function generateFilterConditionSQL(ClassMetadata $targetEntity, $targetTableAlias)
481
    {
482 267
        if (!$this->em->hasFilters()) {
483 264
            return '';
484
        }
485
486 3
        switch($targetEntity->inheritanceType) {
487 3
            case ClassMetadata::INHERITANCE_TYPE_NONE:
488 3
                break;
489
            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
                if ($targetEntity->name !== $targetEntity->rootEntityName) {
493
                    return '';
494
                }
495
                break;
496
            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
                $targetEntity = $this->em->getClassMetadata($targetEntity->rootEntityName);
500
                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 3
        $filterClauses = array();
508 3
        foreach ($this->em->getFilters()->getEnabledFilters() as $filter) {
509 2
            if ('' !== $filterExpr = $filter->addFilterConstraint($targetEntity, $targetTableAlias)) {
510 2
                $filterClauses[] = '(' . $filterExpr . ')';
511
            }
512
        }
513
514 3
        return implode(' AND ', $filterClauses);
515
    }
516
517
    /**
518
     * {@inheritdoc}
519
     */
520 563
    public function walkSelectStatement(AST\SelectStatement $AST)
521
    {
522 563
        $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 563
        $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 563
        $lockMode = $this->query->getHint(Query::HINT_LOCK_MODE);
525 563
        $sql      = $this->walkSelectClause($AST->selectClause)
526 563
            . $this->walkFromClause($AST->fromClause)
527 560
            . $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 557
        if ($AST->groupByClause) {
530 23
            $sql .= $this->walkGroupByClause($AST->groupByClause);
531
        }
532
533 557
        if ($AST->havingClause) {
534 14
            $sql .= $this->walkHavingClause($AST->havingClause);
535
        }
536
537 557
        if ($AST->orderByClause) {
538 116
            $sql .= $this->walkOrderByClause($AST->orderByClause);
539
        }
540
541 556
        if ( ! $AST->orderByClause && ($orderBySql = $this->_generateOrderedCollectionOrderByItems())) {
542 5
            $sql .= ' ORDER BY ' . $orderBySql;
543
        }
544
545 556
        if ($limit !== null || $offset !== null) {
546 35
            $sql = $this->platform->modifyLimitQuery($sql, $limit, $offset);
547
        }
548
549 556
        if ($lockMode === null || $lockMode === false || $lockMode === LockMode::NONE) {
550 551
            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 23
    public function walkUpdateStatement(AST\UpdateStatement $AST)
578
    {
579 23
        $this->useSqlTableAliases = false;
580 23
        $this->rsm->isSelect      = false;
581
582 23
        return $this->walkUpdateClause($AST->updateClause)
583 23
            . $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 34
    public function walkDeleteStatement(AST\DeleteStatement $AST)
590
    {
591 34
        $this->useSqlTableAliases = false;
592 34
        $this->rsm->isSelect      = false;
593
594 34
        return $this->walkDeleteClause($AST->deleteClause)
595 34
            . $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 377
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
628
    {
629 377
        $class = $this->queryComponents[$identificationVariable]['metadata'];
630
631
        if (
632 377
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
633 377
            isset($class->fieldMappings[$fieldName]['inherited'])
634
        ) {
635 35
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
636
        }
637
638 377
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
639
    }
640
641
    /**
642
     * {@inheritdoc}
643
     */
644 439
    public function walkPathExpression($pathExpr)
645
    {
646 439
        $sql = '';
647
648 439
        switch ($pathExpr->type) {
649 439
            case AST\PathExpression::TYPE_STATE_FIELD:
650 423
                $fieldName = $pathExpr->field;
651 423
                $dqlAlias = $pathExpr->identificationVariable;
652 423
                $class = $this->queryComponents[$dqlAlias]['metadata'];
653
654 423
                if ($this->useSqlTableAliases) {
655 377
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
656
                }
657
658 423
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
659 423
                break;
660
661 52
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
662
                // 1- the owning side:
663
                //    Just use the foreign key, i.e. u.group_id
664 52
                $fieldName = $pathExpr->field;
665 52
                $dqlAlias = $pathExpr->identificationVariable;
666 52
                $class = $this->queryComponents[$dqlAlias]['metadata'];
667
668 52
                if (isset($class->associationMappings[$fieldName]['inherited'])) {
669 2
                    $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
670
                }
671
672 52
                $assoc = $class->associationMappings[$fieldName];
673
674 52
                if ( ! $assoc['isOwningSide']) {
675 2
                    throw QueryException::associationPathInverseSideNotSupported();
676
                }
677
678
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
679 50
                if (count($assoc['sourceToTargetKeyColumns']) > 1) {
680 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
681
                }
682
683 49
                if ($this->useSqlTableAliases) {
684 47
                    $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
685
                }
686
687 49
                $sql .= reset($assoc['targetToSourceKeyColumns']);
688 49
                break;
689
690
            default:
691
                throw QueryException::invalidPathExpression($pathExpr);
692
        }
693
694 436
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 563
    public function walkSelectClause($selectClause)
701
    {
702 563
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
703 563
        $sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions));
704
705 563
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
706 1
            $this->query->setHint(self::HINT_DISTINCT, true);
707
        }
708
709 563
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
710 400
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
711
            ||
712 271
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
713 563
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
714
715 563
        foreach ($this->selectedClasses as $selectedClass) {
716 416
            $class       = $selectedClass['class'];
717 416
            $dqlAlias    = $selectedClass['dqlAlias'];
718 416
            $resultAlias = $selectedClass['resultAlias'];
719
720
            // Register as entity or joined entity result
721 416
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
722 416
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
723
            } else {
724 123
                $this->rsm->addJoinedEntityResult(
725 123
                    $class->name,
726
                    $dqlAlias,
727 123
                    $this->queryComponents[$dqlAlias]['parent'],
728 123
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
729
                );
730
            }
731
732 416
            if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
733
                // Add discriminator columns to SQL
734 78
                $rootClass   = $this->em->getClassMetadata($class->rootEntityName);
735 78
                $tblAlias    = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
736 78
                $discrColumn = $rootClass->discriminatorColumn;
737 78
                $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
738
739 78
                $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
740
741 78
                $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
742 78
                $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], false, $discrColumn['type']);
743
            }
744
745
            // Add foreign key columns to SQL, if necessary
746 416
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
747 170
                continue;
748
            }
749
750
            // Add foreign key columns of class and also parent classes
751 255
            foreach ($class->associationMappings as $assoc) {
752 225
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
753 192
                    continue;
754 197
                } else if ( !$addMetaColumns && !isset($assoc['id'])) {
755
                    continue;
756
                }
757
758 197
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
759 197
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
760 197
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
761 197
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
762
763 197
                foreach ($assoc['joinColumns'] as $joinColumn) {
764 197
                    $columnName  = $joinColumn['name'];
765 197
                    $columnAlias = $this->getSQLColumnAlias($columnName);
766 197
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
767
768 197
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
769
770 197
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
771
                }
772
            }
773
774
            // Add foreign key columns to SQL, if necessary
775 255
            if ( ! $addMetaColumns) {
776 2
                continue;
777
            }
778
779
            // Add foreign key columns of subclasses
780 253
            foreach ($class->subClasses as $subClassName) {
781 22
                $subClass      = $this->em->getClassMetadata($subClassName);
782 22
                $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
783
784 22
                foreach ($subClass->associationMappings as $assoc) {
785
                    // Skip if association is inherited
786 21
                    if (isset($assoc['inherited'])) continue;
787
788 14
                    if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
789 12
                        $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
790
791 12
                        foreach ($assoc['joinColumns'] as $joinColumn) {
792 12
                            $columnName  = $joinColumn['name'];
793 12
                            $columnAlias = $this->getSQLColumnAlias($columnName);
794 12
                            $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
795
796 12
                            $sqlSelectExpressions[] = $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
797
798 253
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
799
                        }
800
                    }
801
                }
802
            }
803
        }
804
805 563
        $sql .= implode(', ', $sqlSelectExpressions);
806
807 563
        return $sql;
808
    }
809
810
    /**
811
     * {@inheritdoc}
812
     */
813 565
    public function walkFromClause($fromClause)
814
    {
815 565
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
816 565
        $sqlParts = array();
817
818 565
        foreach ($identificationVarDecls as $identificationVariableDecl) {
819 565
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
820
        }
821
822 562
        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 566
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
833
    {
834 566
        $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 566
        if ($identificationVariableDecl->indexBy) {
837 6
            $this->walkIndexBy($identificationVariableDecl->indexBy);
838
        }
839
840 566
        foreach ($identificationVariableDecl->joins as $join) {
841 217
            $sql .= $this->walkJoin($join);
842
        }
843
844 563
        return $sql;
845
    }
846
847
    /**
848
     * Walks down a IndexBy AST node.
849
     *
850
     * @param AST\IndexBy $indexBy
851
     *
852
     * @return void
853
     */
854 9
    public function walkIndexBy($indexBy)
855
    {
856 9
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
857 9
        $alias          = $pathExpression->identificationVariable;
858 9
        $field          = $pathExpression->field;
859
860 9
        if (isset($this->scalarFields[$alias][$field])) {
861
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
862
863
            return;
864
        }
865
866 9
        $this->rsm->addIndexBy($alias, $field);
867 9
    }
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 566
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
877
    {
878 566
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
879 566
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
880
881 566
        if ($rangeVariableDeclaration->isRoot) {
882 566
            $this->rootAliases[] = $dqlAlias;
883
        }
884
885 566
        $sql = $this->platform->appendLockHint(
886 566
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
887 566
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
888 566
            $this->query->getHint(Query::HINT_LOCK_MODE)
889
        );
890
891 566
        if ($class->isInheritanceTypeJoined()) {
892 82
            $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
893
        }
894
895 566
        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 200
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
910
    {
911 200
        $sql = '';
912
913 200
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
914 200
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
915 200
        $indexBy                   = $joinAssociationDeclaration->indexBy;
916
917 200
        $relation        = $this->queryComponents[$joinedDqlAlias]['relation'];
918 200
        $targetClass     = $this->em->getClassMetadata($relation['targetEntity']);
919 200
        $sourceClass     = $this->em->getClassMetadata($relation['sourceEntity']);
920 200
        $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
921
922 200
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
923 200
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
924
925
        // Ensure we got the owning side, since it has all mapping info
926 200
        $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
927
928 200
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
929 3
            if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
930 3
                throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
931
            }
932
        }
933
934 197
        $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 197
            case ($assoc['type'] & ClassMetadata::TO_ONE):
941 155
                $conditions = array();
942
943 155
                foreach ($assoc['joinColumns'] as $joinColumn) {
944 155
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
945 155
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
946
947 155
                    if ($relation['isOwningSide']) {
948 88
                        $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
949
950 88
                        continue;
951
                    }
952
953 95
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
954
                }
955
956
                // Apply remaining inheritance restrictions
957 155
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
958
959 155
                if ($discrSql) {
960 2
                    $conditions[] = $discrSql;
961
                }
962
963
                // Apply the filters
964 155
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
965
966 155
                if ($filterExpr) {
967
                    $conditions[] = $filterExpr;
968
                }
969
970
                $targetTableJoin = array(
971 155
                    'table' => $targetTableName . ' ' . $targetTableAlias,
972 155
                    'condition' => implode(' AND ', $conditions),
973
                );
974 155
                break;
975
976 51
            case ($assoc['type'] == ClassMetadata::MANY_TO_MANY):
977
                // Join relation table
978 51
                $joinTable      = $assoc['joinTable'];
979 51
                $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
980 51
                $joinTableName  = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
981
982 51
                $conditions      = array();
983 51
                $relationColumns = ($relation['isOwningSide'])
984 43
                    ? $assoc['joinTable']['joinColumns']
985 51
                    : $assoc['joinTable']['inverseJoinColumns'];
986
987 51
                foreach ($relationColumns as $joinColumn) {
988 51
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
989 51
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
990
991 51
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
992
                }
993
994 51
                $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
995
996
                // Join target table
997 51
                $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
998
999 51
                $conditions      = array();
1000 51
                $relationColumns = ($relation['isOwningSide'])
1001 43
                    ? $assoc['joinTable']['inverseJoinColumns']
1002 51
                    : $assoc['joinTable']['joinColumns'];
1003
1004 51
                foreach ($relationColumns as $joinColumn) {
1005 51
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1006 51
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1007
1008 51
                    $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1009
                }
1010
1011
                // Apply remaining inheritance restrictions
1012 51
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
1013
1014 51
                if ($discrSql) {
1015 1
                    $conditions[] = $discrSql;
1016
                }
1017
1018
                // Apply the filters
1019 51
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1020
1021 51
                if ($filterExpr) {
1022
                    $conditions[] = $filterExpr;
1023
                }
1024
1025
                $targetTableJoin = array(
1026 51
                    'table' => $targetTableName . ' ' . $targetTableAlias,
1027 51
                    'condition' => implode(' AND ', $conditions),
1028
                );
1029 51
                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 197
        $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1037
1038 197
        if ($targetClass->isInheritanceTypeJoined()) {
1039 8
            $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 8
            if ($withCondition) {
1042 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1043
            } else {
1044 8
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1045
            }
1046
        } else {
1047 189
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1048
        }
1049
1050 197
        if ($withCondition) {
1051 4
            $sql .= ' AND ' . $withCondition;
1052
        }
1053
1054
        // Apply the indexes
1055 197
        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 192
        } else if (isset($relation['indexBy'])) {
1059 2
            $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
1060
        }
1061
1062 197
        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 127
    public function walkOrderByClause($orderByClause)
1077
    {
1078 127
        $orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems);
1079
1080 126
        if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
1081
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1082
        }
1083
1084 126
        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 217
    public function walkJoin($join)
1119
    {
1120 217
        $joinType        = $join->joinType;
1121 217
        $joinDeclaration = $join->joinAssociationDeclaration;
1122
1123 217
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1124 48
            ? ' LEFT JOIN '
1125 217
            : ' INNER JOIN ';
1126
1127
        switch (true) {
1128 217
            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 200
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1166 197
                break;
1167
        }
1168
1169 214
        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 563
    public function walkSelectExpression($selectExpression)
1287
    {
1288 563
        $sql    = '';
1289 563
        $expr   = $selectExpression->expression;
1290 563
        $hidden = $selectExpression->hiddenAliasResultVariable;
1291
1292
        switch (true) {
1293 563
            case ($expr instanceof AST\PathExpression):
1294 98
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1295
                    throw QueryException::invalidPathExpression($expr);
1296
                }
1297
1298 98
                $fieldName = $expr->field;
1299 98
                $dqlAlias  = $expr->identificationVariable;
1300 98
                $qComp     = $this->queryComponents[$dqlAlias];
1301 98
                $class     = $qComp['metadata'];
1302
1303 98
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1304 98
                $tableName   = ($class->isInheritanceTypeJoined())
1305 10
                    ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1306 98
                    : $class->getTableName();
1307
1308 98
                $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1309 98
                $fieldMapping  = $class->fieldMappings[$fieldName];
1310 98
                $columnName    = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1311 98
                $columnAlias   = $this->getSQLColumnAlias($fieldMapping['columnName']);
1312 98
                $col           = $sqlTableAlias . '.' . $columnName;
1313
1314 98
                if (isset($fieldMapping['requireSQLConversion'])) {
1315 1
                    $type = Type::getType($fieldMapping['type']);
1316 1
                    $col  = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1317
                }
1318
1319 98
                $sql .= $col . ' AS ' . $columnAlias;
1320
1321 98
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1322
1323 98
                if ( ! $hidden) {
1324 98
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
1325 98
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1326
                }
1327
1328 98
                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 35
            case ($expr instanceof AST\NullIfExpression):
1338 35
            case ($expr instanceof AST\CoalesceExpression):
1339 9
            case ($expr instanceof AST\GeneralCaseExpression):
1340 41
            case ($expr instanceof AST\SimpleCaseExpression):
1341 101
                $columnAlias = $this->getSQLColumnAlias('sclr');
1342 101
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1343
1344 101
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1345
1346 101
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1347
1348 101
                if ( ! $hidden) {
1349
                    // We cannot resolve field type here; assume 'string'.
1350 100
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1351
                }
1352 101
                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 416
            case ($expr instanceof AST\NewObjectExpression):
1369 22
                $sql .= $this->walkNewObject($expr,$selectExpression->fieldIdentificationVariable);
1370 22
                break;
1371
1372
            default:
1373
                // IdentificationVariable or PartialObjectExpression
1374 416
                if ($expr instanceof AST\PartialObjectExpression) {
1375 14
                    $dqlAlias = $expr->identificationVariable;
1376 14
                    $partialFieldSet = $expr->partialFieldSet;
1377
                } else {
1378 412
                    $dqlAlias = $expr;
1379 412
                    $partialFieldSet = array();
1380
                }
1381
1382 416
                $queryComp   = $this->queryComponents[$dqlAlias];
1383 416
                $class       = $queryComp['metadata'];
1384 416
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1385
1386 416
                if ( ! isset($this->selectedClasses[$dqlAlias])) {
1387 416
                    $this->selectedClasses[$dqlAlias] = array(
1388 416
                        'class'       => $class,
1389 416
                        'dqlAlias'    => $dqlAlias,
1390 416
                        'resultAlias' => $resultAlias
1391
                    );
1392
                }
1393
1394 416
                $sqlParts = array();
1395
1396
                // Select all fields from the queried class
1397 416
                foreach ($class->fieldMappings as $fieldName => $mapping) {
1398 415
                    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 12
                        continue;
1400
                    }
1401
1402 415
                    $tableName = (isset($mapping['inherited']))
1403 45
                        ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1404 415
                        : $class->getTableName();
1405
1406 415
                    $sqlTableAlias    = $this->getSQLTableAlias($tableName, $dqlAlias);
1407 415
                    $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1408 415
                    $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1409
1410 415
                    $col = $sqlTableAlias . '.' . $quotedColumnName;
1411
1412 415
                    if (isset($mapping['requireSQLConversion'])) {
1413 4
                        $type = Type::getType($mapping['type']);
1414 4
                        $col = $type->convertToPHPValueSQL($col, $this->platform);
1415
                    }
1416
1417 415
                    $sqlParts[] = $col . ' AS '. $columnAlias;
1418
1419 415
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1420
1421 415
                    $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 416
                if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1429 322
                    foreach ($class->subClasses as $subClassName) {
1430 33
                        $subClass      = $this->em->getClassMetadata($subClassName);
1431 33
                        $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
1432
1433 33
                        foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1434 33
                            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 33
                                continue;
1436
                            }
1437
1438 29
                            $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1439 29
                            $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
1440
1441 29
                            $col = $sqlTableAlias . '.' . $quotedColumnName;
1442
1443 29
                            if (isset($mapping['requireSQLConversion'])) {
1444
                                $type = Type::getType($mapping['type']);
1445
                                $col = $type->convertToPHPValueSQL($col, $this->platform);
1446
                            }
1447
1448 29
                            $sqlParts[] = $col . ' AS ' . $columnAlias;
1449
1450 29
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1451
1452 33
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1453
                        }
1454
                    }
1455
                }
1456
1457 416
                $sql .= implode(', ', $sqlParts);
1458
        }
1459
1460 563
        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 69
    public function walkAggregateExpression($aggExpression)
1656
    {
1657 69
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1658 69
            . $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 34
    public function walkDeleteClause(AST\DeleteClause $deleteClause)
1726
    {
1727 34
        $class     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1728 34
        $tableName = $class->getTableName();
1729 34
        $sql       = 'DELETE FROM ' . $this->quoteStrategy->getTableName($class, $this->platform);
1730
1731 34
        $this->setSQLTableAlias($tableName, $tableName, $deleteClause->aliasIdentificationVariable);
1732 34
        $this->rootAliases[] = $deleteClause->aliasIdentificationVariable;
1733
1734 34
        return $sql;
1735
    }
1736
1737
    /**
1738
     * {@inheritdoc}
1739
     */
1740 23
    public function walkUpdateClause($updateClause)
1741
    {
1742 23
        $class     = $this->em->getClassMetadata($updateClause->abstractSchemaName);
1743 23
        $tableName = $class->getTableName();
1744 23
        $sql       = 'UPDATE ' . $this->quoteStrategy->getTableName($class, $this->platform);
1745
1746 23
        $this->setSQLTableAlias($tableName, $tableName, $updateClause->aliasIdentificationVariable);
1747 23
        $this->rootAliases[] = $updateClause->aliasIdentificationVariable;
1748
1749 23
        $sql .= ' SET ' . implode(', ', array_map(array($this, 'walkUpdateItem'), $updateClause->updateItems));
1750
1751 23
        return $sql;
1752
    }
1753
1754
    /**
1755
     * {@inheritdoc}
1756
     */
1757 26
    public function walkUpdateItem($updateItem)
1758
    {
1759 26
        $useTableAliasesBefore = $this->useSqlTableAliases;
1760 26
        $this->useSqlTableAliases = false;
1761
1762 26
        $sql      = $this->walkPathExpression($updateItem->pathExpression) . ' = ';
1763 26
        $newValue = $updateItem->newValue;
1764
1765
        switch (true) {
1766 26
            case ($newValue instanceof AST\Node):
1767 25
                $sql .= $newValue->dispatch($this);
1768 25
                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 26
        $this->useSqlTableAliases = $useTableAliasesBefore;
1780
1781 26
        return $sql;
1782
    }
1783
1784
    /**
1785
     * {@inheritdoc}
1786
     */
1787 618
    public function walkWhereClause($whereClause)
1788
    {
1789 618
        $condSql  = null !== $whereClause ? $this->walkConditionalExpression($whereClause->conditionalExpression) : '';
1790 615
        $discrSql = $this->_generateDiscriminatorColumnConditionSql($this->rootAliases);
1791
1792 615
        if ($this->em->hasFilters()) {
1793 3
            $filterClauses = array();
1794 3
            foreach ($this->rootAliases as $dqlAlias) {
1795 3
                $class = $this->queryComponents[$dqlAlias]['metadata'];
1796 3
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1797
1798 3
                if ($filterExpr = $this->generateFilterConditionSQL($class, $tableAlias)) {
1799 3
                    $filterClauses[] = $filterExpr;
1800
                }
1801
            }
1802
1803 3
            if (count($filterClauses)) {
1804 1
                if ($condSql) {
1805
                    $condSql = '(' . $condSql . ') AND ';
1806
                }
1807
1808 1
                $condSql .= implode(' AND ', $filterClauses);
1809
            }
1810
        }
1811
1812 615
        if ($condSql) {
1813 244
            return ' WHERE ' . (( ! $discrSql) ? $condSql : '(' . $condSql . ') AND ' . $discrSql);
1814
        }
1815
1816 396
        if ($discrSql) {
1817 18
            return ' WHERE ' . $discrSql;
1818
        }
1819
1820 380
        return '';
1821
    }
1822
1823
    /**
1824
     * {@inheritdoc}
1825
     */
1826 278
    public function walkConditionalExpression($condExpr)
1827
    {
1828
        // Phase 2 AST optimization: Skip processing of ConditionalExpression
1829
        // if only one ConditionalTerm is defined
1830 278
        if ( ! ($condExpr instanceof AST\ConditionalExpression)) {
1831 266
            return $this->walkConditionalTerm($condExpr);
1832
        }
1833
1834 24
        return implode(' OR ', array_map(array($this, 'walkConditionalTerm'), $condExpr->conditionalTerms));
1835
    }
1836
1837
    /**
1838
     * {@inheritdoc}
1839
     */
1840 278
    public function walkConditionalTerm($condTerm)
1841
    {
1842
        // Phase 2 AST optimization: Skip processing of ConditionalTerm
1843
        // if only one ConditionalFactor is defined
1844 278
        if ( ! ($condTerm instanceof AST\ConditionalTerm)) {
1845 258
            return $this->walkConditionalFactor($condTerm);
1846
        }
1847
1848 36
        return implode(' AND ', array_map(array($this, 'walkConditionalFactor'), $condTerm->conditionalFactors));
1849
    }
1850
1851
    /**
1852
     * {@inheritdoc}
1853
     */
1854 278
    public function walkConditionalFactor($factor)
1855
    {
1856
        // Phase 2 AST optimization: Skip processing of ConditionalFactor
1857
        // if only one ConditionalPrimary is defined
1858 278
        return ( ! ($factor instanceof AST\ConditionalFactor))
1859 275
            ? $this->walkConditionalPrimary($factor)
1860 275
            : ($factor->not ? 'NOT ' : '') . $this->walkConditionalPrimary($factor->conditionalPrimary);
1861
    }
1862
1863
    /**
1864
     * {@inheritdoc}
1865
     */
1866 278
    public function walkConditionalPrimary($primary)
1867
    {
1868 278
        if ($primary->isSimpleConditionalExpression()) {
1869 278
            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 34
    public function walkInExpression($inExpr)
2037
    {
2038 34
        $sql = $this->walkArithmeticExpression($inExpr->expression) . ($inExpr->not ? ' NOT' : '') . ' IN (';
2039
2040 33
        $sql .= ($inExpr->subselect)
2041 7
            ? $this->walkSubselect($inExpr->subselect)
2042 33
            : implode(', ', array_map(array($this, 'walkInParameter'), $inExpr->literals));
2043
2044 33
        $sql .= ')';
2045
2046 33
        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 26
    public function walkInParameter($inParam)
2106
    {
2107 26
        return $inParam instanceof AST\InputParameter
2108 19
            ? $this->walkInputParameter($inParam)
2109 26
            : $this->walkLiteral($inParam);
2110
    }
2111
2112
    /**
2113
     * {@inheritdoc}
2114
     */
2115 139
    public function walkLiteral($literal)
2116
    {
2117 139
        switch ($literal->type) {
2118 139
            case AST\Literal::STRING:
2119 47
                return $this->conn->quote($literal->value);
2120
2121 105
            case AST\Literal::BOOLEAN:
2122 8
                $bool = strtolower($literal->value) == 'true' ? true : false;
2123 8
                $boolVal = $this->conn->getDatabasePlatform()->convertBooleans($bool);
2124
2125 8
                return $boolVal;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $boolVal; (object|integer|double|string|null|array) is incompatible with the return type declared by the interface Doctrine\ORM\Query\TreeWalker::walkLiteral of type string.

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
2126
2127 98
            case AST\Literal::NUMERIC:
2128 98
                return $literal->value;
2129
2130
            default:
2131
                throw QueryException::invalidLiteral($literal);
2132
        }
2133
    }
2134
2135
    /**
2136
     * {@inheritdoc}
2137
     */
2138 6
    public function walkBetweenExpression($betweenExpr)
2139
    {
2140 6
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2141
2142 6
        if ($betweenExpr->not) {
2143 2
            $sql .= ' NOT';
2144
        }
2145
2146 6
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2147 6
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2148
2149 6
        return $sql;
2150
    }
2151
2152
    /**
2153
     * {@inheritdoc}
2154
     */
2155 9
    public function walkLikeExpression($likeExpr)
2156
    {
2157 9
        $stringExpr = $likeExpr->stringExpression;
2158 9
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2159 1
            ? $this->walkResultVariable($stringExpr)
2160 9
            : $stringExpr->dispatch($this);
2161
2162 9
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2163
2164 9
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2165 4
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2166 6
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2167 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2168 6
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2169 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2170
        } else {
2171 6
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2172
        }
2173
2174 9
        if ($likeExpr->escapeChar) {
2175 1
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2176
        }
2177
2178 9
        return $sql;
2179
    }
2180
2181
    /**
2182
     * {@inheritdoc}
2183
     */
2184 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2185
    {
2186 5
        return $this->walkPathExpression($stateFieldPathExpression);
2187
    }
2188
2189
    /**
2190
     * {@inheritdoc}
2191
     */
2192 222
    public function walkComparisonExpression($compExpr)
2193
    {
2194 222
        $leftExpr  = $compExpr->leftExpression;
2195 222
        $rightExpr = $compExpr->rightExpression;
2196 222
        $sql       = '';
2197
2198 222
        $sql .= ($leftExpr instanceof AST\Node)
2199 222
            ? $leftExpr->dispatch($this)
2200 221
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2201
2202 221
        $sql .= ' ' . $compExpr->operator . ' ';
2203
2204 221
        $sql .= ($rightExpr instanceof AST\Node)
2205 219
            ? $rightExpr->dispatch($this)
2206 221
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2207
2208 221
        return $sql;
2209
    }
2210
2211
    /**
2212
     * {@inheritdoc}
2213
     */
2214 134
    public function walkInputParameter($inputParam)
2215
    {
2216 134
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2217
2218 134
        $parameter = $this->query->getParameter($inputParam->name);
2219
2220 134
        if ($parameter && Type::hasType($type = $parameter->getType())) {
2221 11
            return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2222
        }
2223
2224 125
        return '?';
2225
    }
2226
2227
    /**
2228
     * {@inheritdoc}
2229
     */
2230 241
    public function walkArithmeticExpression($arithmeticExpr)
2231
    {
2232 241
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2233 241
            ? $this->walkSimpleArithmeticExpression($arithmeticExpr->simpleArithmeticExpression)
0 ignored issues
show
Bug introduced by
It seems like $arithmeticExpr->simpleArithmeticExpression can be null; however, walkSimpleArithmeticExpression() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2235
    }
2236
2237
    /**
2238
     * {@inheritdoc}
2239
     */
2240 300
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2241
    {
2242 300
        if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2243 293
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2244
        }
2245
2246 27
        return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms));
2247
    }
2248
2249
    /**
2250
     * {@inheritdoc}
2251
     */
2252 327
    public function walkArithmeticTerm($term)
2253
    {
2254 327
        if (is_string($term)) {
2255 21
            return (isset($this->queryComponents[$term]))
2256 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2257 21
                : $term;
2258
        }
2259
2260
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2261
        // if only one ArithmeticFactor is defined
2262 326
        if ( ! ($term instanceof AST\ArithmeticTerm)) {
2263 298
            return $this->walkArithmeticFactor($term);
2264
        }
2265
2266 47
        return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors));
2267
    }
2268
2269
    /**
2270
     * {@inheritdoc}
2271
     */
2272 327
    public function walkArithmeticFactor($factor)
2273
    {
2274 327
        if (is_string($factor)) {
2275 47
            return (isset($this->queryComponents[$factor]))
2276 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2277 47
                : $factor;
2278
        }
2279
2280
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2281
        // if only one ArithmeticPrimary is defined
2282 327
        if ( ! ($factor instanceof AST\ArithmeticFactor)) {
2283 326
            return $this->walkArithmeticPrimary($factor);
2284
        }
2285
2286 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2287
2288 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2289
    }
2290
2291
    /**
2292
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2293
     *
2294
     * @param mixed $primary
2295
     *
2296
     * @return string The SQL.
2297
     */
2298 327
    public function walkArithmeticPrimary($primary)
2299
    {
2300 327
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2301
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2302
        }
2303
2304 327
        if ($primary instanceof AST\Node) {
2305 327
            return $primary->dispatch($this);
2306
        }
2307
2308
        return $this->walkEntityIdentificationVariable($primary);
2309
    }
2310
2311
    /**
2312
     * {@inheritdoc}
2313
     */
2314 18
    public function walkStringPrimary($stringPrimary)
2315
    {
2316 18
        return (is_string($stringPrimary))
2317
            ? $this->conn->quote($stringPrimary)
2318 18
            : $stringPrimary->dispatch($this);
2319
    }
2320
2321
    /**
2322
     * {@inheritdoc}
2323
     */
2324 30
    public function walkResultVariable($resultVariable)
2325
    {
2326 30
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2327
2328 30
        if (is_array($resultAlias)) {
2329 1
            return implode(', ', $resultAlias);
2330
        }
2331
2332 29
        return $resultAlias;
2333
    }
2334
}
2335