Completed
Push — master ( e7f2e3...a9230b )
by Marco
09:57 queued 03:00
created

SqlWalker::walkLiteral()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218

Importance

Changes 0
Metric Value
dl 0
loc 16
ccs 8
cts 9
cp 0.8889
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 1
crap 4.0218
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 684
    public function __construct($query, $parserResult, array $queryComponents)
182
    {
183 684
        $this->query            = $query;
184 684
        $this->parserResult     = $parserResult;
185 684
        $this->queryComponents  = $queryComponents;
186 684
        $this->rsm              = $parserResult->getResultSetMapping();
187 684
        $this->em               = $query->getEntityManager();
188 684
        $this->conn             = $this->em->getConnection();
189 684
        $this->platform         = $this->conn->getDatabasePlatform();
190 684
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
191 684
    }
192
193
    /**
194
     * Gets the Query instance used by the walker.
195
     *
196
     * @return Query.
0 ignored issues
show
Documentation introduced by
The doc-type Query. could not be parsed: Unknown type name "Query." at position 0. (view supported doc-types)

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

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

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

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
596
    }
597
598
    /**
599
     * Walks down an IdentificationVariable AST node, thereby generating the appropriate SQL.
600
     * This one differs of ->walkIdentificationVariable() because it generates the entity identifiers.
601
     *
602
     * @param string $identVariable
603
     *
604
     * @return string
605
     */
606 2
    public function walkEntityIdentificationVariable($identVariable)
607
    {
608 2
        $class      = $this->queryComponents[$identVariable]['metadata'];
609 2
        $tableAlias = $this->getSQLTableAlias($class->getTableName(), $identVariable);
610 2
        $sqlParts   = array();
611
612 2
        foreach ($this->quoteStrategy->getIdentifierColumnNames($class, $this->platform) as $columnName) {
613 2
            $sqlParts[] = $tableAlias . '.' . $columnName;
614
        }
615
616 2
        return implode(', ', $sqlParts);
617
    }
618
619
    /**
620
     * Walks down an IdentificationVariable (no AST node associated), thereby generating the SQL.
621
     *
622
     * @param string $identificationVariable
623
     * @param string $fieldName
624
     *
625
     * @return string The SQL.
626
     */
627 418
    public function walkIdentificationVariable($identificationVariable, $fieldName = null)
628
    {
629 418
        $class = $this->queryComponents[$identificationVariable]['metadata'];
630
631
        if (
632 418
            $fieldName !== null && $class->isInheritanceTypeJoined() &&
633 418
            isset($class->fieldMappings[$fieldName]['inherited'])
634
        ) {
635 37
            $class = $this->em->getClassMetadata($class->fieldMappings[$fieldName]['inherited']);
636
        }
637
638 418
        return $this->getSQLTableAlias($class->getTableName(), $identificationVariable);
639
    }
640
641
    /**
642
     * {@inheritdoc}
643
     */
644 487
    public function walkPathExpression($pathExpr)
645
    {
646 487
        $sql = '';
647
648 487
        switch ($pathExpr->type) {
649 487
            case AST\PathExpression::TYPE_STATE_FIELD:
650 466
                $fieldName = $pathExpr->field;
651 466
                $dqlAlias = $pathExpr->identificationVariable;
652 466
                $class = $this->queryComponents[$dqlAlias]['metadata'];
653
654 466
                if ($this->useSqlTableAliases) {
655 418
                    $sql .= $this->walkIdentificationVariable($dqlAlias, $fieldName) . '.';
656
                }
657
658 466
                $sql .= $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
659 466
                break;
660
661 62
            case AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION:
662
                // 1- the owning side:
663
                //    Just use the foreign key, i.e. u.group_id
664 62
                $fieldName = $pathExpr->field;
665 62
                $dqlAlias = $pathExpr->identificationVariable;
666 62
                $class = $this->queryComponents[$dqlAlias]['metadata'];
667
668 62
                if (isset($class->associationMappings[$fieldName]['inherited'])) {
669 2
                    $class = $this->em->getClassMetadata($class->associationMappings[$fieldName]['inherited']);
670
                }
671
672 62
                $assoc = $class->associationMappings[$fieldName];
673
674 62
                if ( ! $assoc['isOwningSide']) {
675 2
                    throw QueryException::associationPathInverseSideNotSupported();
676
                }
677
678
                // COMPOSITE KEYS NOT (YET?) SUPPORTED
679 60
                if (count($assoc['sourceToTargetKeyColumns']) > 1) {
680 1
                    throw QueryException::associationPathCompositeKeyNotSupported();
681
                }
682
683 59
                if ($this->useSqlTableAliases) {
684 56
                    $sql .= $this->getSQLTableAlias($class->getTableName(), $dqlAlias) . '.';
685
                }
686
687 59
                $sql .= reset($assoc['targetToSourceKeyColumns']);
688 59
                break;
689
690
            default:
691
                throw QueryException::invalidPathExpression($pathExpr);
692
        }
693
694 484
        return $sql;
695
    }
696
697
    /**
698
     * {@inheritdoc}
699
     */
700 618
    public function walkSelectClause($selectClause)
701
    {
702 618
        $sql = 'SELECT ' . (($selectClause->isDistinct) ? 'DISTINCT ' : '');
703 618
        $sqlSelectExpressions = array_filter(array_map(array($this, 'walkSelectExpression'), $selectClause->selectExpressions));
704
705 618
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && $selectClause->isDistinct) {
706 1
            $this->query->setHint(self::HINT_DISTINCT, true);
707
        }
708
709 618
        $addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
710 455
            $this->query->getHydrationMode() == Query::HYDRATE_OBJECT
711
            ||
712 289
            $this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
713 618
            $this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
714
715 618
        foreach ($this->selectedClasses as $selectedClass) {
716 482
            $class       = $selectedClass['class'];
717 482
            $dqlAlias    = $selectedClass['dqlAlias'];
718 482
            $resultAlias = $selectedClass['resultAlias'];
719
720
            // Register as entity or joined entity result
721 482
            if ($this->queryComponents[$dqlAlias]['relation'] === null) {
722 482
                $this->rsm->addEntityResult($class->name, $dqlAlias, $resultAlias);
723
            } else {
724 158
                $this->rsm->addJoinedEntityResult(
725 158
                    $class->name,
726
                    $dqlAlias,
727 158
                    $this->queryComponents[$dqlAlias]['parent'],
728 158
                    $this->queryComponents[$dqlAlias]['relation']['fieldName']
729
                );
730
            }
731
732 482
            if ($class->isInheritanceTypeSingleTable() || $class->isInheritanceTypeJoined()) {
733
                // Add discriminator columns to SQL
734 88
                $rootClass   = $this->em->getClassMetadata($class->rootEntityName);
735 88
                $tblAlias    = $this->getSQLTableAlias($rootClass->getTableName(), $dqlAlias);
736 88
                $discrColumn = $rootClass->discriminatorColumn;
737 88
                $columnAlias = $this->getSQLColumnAlias($discrColumn['name']);
738
739 88
                $sqlSelectExpressions[] = $tblAlias . '.' . $discrColumn['name'] . ' AS ' . $columnAlias;
740
741 88
                $this->rsm->setDiscriminatorColumn($dqlAlias, $columnAlias);
742 88
                $this->rsm->addMetaResult($dqlAlias, $columnAlias, $discrColumn['fieldName'], false, $discrColumn['type']);
743
            }
744
745
            // Add foreign key columns to SQL, if necessary
746 482
            if ( ! $addMetaColumns && ! $class->containsForeignIdentifier) {
747 181
                continue;
748
            }
749
750
            // Add foreign key columns of class and also parent classes
751 351
            foreach ($class->associationMappings as $assoc) {
752 313
                if ( ! ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE)) {
753 265
                    continue;
754 281
                } else if ( !$addMetaColumns && !isset($assoc['id'])) {
755
                    continue;
756
                }
757
758 281
                $targetClass   = $this->em->getClassMetadata($assoc['targetEntity']);
759 281
                $isIdentifier  = (isset($assoc['id']) && $assoc['id'] === true);
760 281
                $owningClass   = (isset($assoc['inherited'])) ? $this->em->getClassMetadata($assoc['inherited']) : $class;
761 281
                $sqlTableAlias = $this->getSQLTableAlias($owningClass->getTableName(), $dqlAlias);
762
763 281
                foreach ($assoc['joinColumns'] as $joinColumn) {
764 281
                    $columnName  = $joinColumn['name'];
765 281
                    $columnAlias = $this->getSQLColumnAlias($columnName);
766 281
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
767
768 281
                    $sqlSelectExpressions[] = $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
769
770 281
                    $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $isIdentifier, $columnType);
771
                }
772
            }
773
774
            // Add foreign key columns to SQL, if necessary
775 351
            if ( ! $addMetaColumns) {
776 8
                continue;
777
            }
778
779
            // Add foreign key columns of subclasses
780 346
            foreach ($class->subClasses as $subClassName) {
781 30
                $subClass      = $this->em->getClassMetadata($subClassName);
782 30
                $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
783
784 30
                foreach ($subClass->associationMappings as $assoc) {
785
                    // Skip if association is inherited
786 26
                    if (isset($assoc['inherited'])) continue;
787
788 15
                    if ($assoc['isOwningSide'] && $assoc['type'] & ClassMetadata::TO_ONE) {
789 13
                        $targetClass = $this->em->getClassMetadata($assoc['targetEntity']);
790
791 13
                        foreach ($assoc['joinColumns'] as $joinColumn) {
792 13
                            $columnName  = $joinColumn['name'];
793 13
                            $columnAlias = $this->getSQLColumnAlias($columnName);
794 13
                            $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
795
796 13
                            $sqlSelectExpressions[] = $sqlTableAlias . '.' . $columnName . ' AS ' . $columnAlias;
797
798 346
                            $this->rsm->addMetaResult($dqlAlias, $columnAlias, $columnName, $subClass->isIdentifier($columnName), $columnType);
799
                        }
800
                    }
801
                }
802
            }
803
        }
804
805 618
        $sql .= implode(', ', $sqlSelectExpressions);
806
807 618
        return $sql;
808
    }
809
810
    /**
811
     * {@inheritdoc}
812
     */
813 620
    public function walkFromClause($fromClause)
814
    {
815 620
        $identificationVarDecls = $fromClause->identificationVariableDeclarations;
816 620
        $sqlParts = array();
817
818 620
        foreach ($identificationVarDecls as $identificationVariableDecl) {
819 620
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($identificationVariableDecl);
820
        }
821
822 617
        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 621
    public function walkIdentificationVariableDeclaration($identificationVariableDecl)
833
    {
834 621
        $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 621
        if ($identificationVariableDecl->indexBy) {
837 5
            $this->walkIndexBy($identificationVariableDecl->indexBy);
838
        }
839
840 621
        foreach ($identificationVariableDecl->joins as $join) {
841 244
            $sql .= $this->walkJoin($join);
842
        }
843
844 618
        return $sql;
845
    }
846
847
    /**
848
     * Walks down a IndexBy AST node.
849
     *
850
     * @param AST\IndexBy $indexBy
851
     *
852
     * @return void
853
     */
854 8
    public function walkIndexBy($indexBy)
855
    {
856 8
        $pathExpression = $indexBy->simpleStateFieldPathExpression;
857 8
        $alias          = $pathExpression->identificationVariable;
858 8
        $field          = $pathExpression->field;
859
860 8
        if (isset($this->scalarFields[$alias][$field])) {
861
            $this->rsm->addIndexByScalar($this->scalarFields[$alias][$field]);
862
863
            return;
864
        }
865
866 8
        $this->rsm->addIndexBy($alias, $field);
867 8
    }
868
869
    /**
870
     * Walks down a RangeVariableDeclaration AST node, thereby generating the appropriate SQL.
871
     *
872
     * @param AST\RangeVariableDeclaration $rangeVariableDeclaration
873
     *
874
     * @return string
875
     */
876 621
    public function walkRangeVariableDeclaration($rangeVariableDeclaration)
877
    {
878 621
        $class    = $this->em->getClassMetadata($rangeVariableDeclaration->abstractSchemaName);
879 621
        $dqlAlias = $rangeVariableDeclaration->aliasIdentificationVariable;
880
881 621
        if ($rangeVariableDeclaration->isRoot) {
882 621
            $this->rootAliases[] = $dqlAlias;
883
        }
884
885 621
        $sql = $this->platform->appendLockHint(
886 621
            $this->quoteStrategy->getTableName($class, $this->platform) . ' ' .
887 621
            $this->getSQLTableAlias($class->getTableName(), $dqlAlias),
888 621
            $this->query->getHint(Query::HINT_LOCK_MODE)
889
        );
890
891 621
        if ($class->isInheritanceTypeJoined()) {
892 89
            $sql .= $this->_generateClassTableInheritanceJoins($class, $dqlAlias);
893
        }
894
895 621
        return $sql;
896
    }
897
898
    /**
899
     * Walks down a JoinAssociationDeclaration AST node, thereby generating the appropriate SQL.
900
     *
901
     * @param AST\JoinAssociationDeclaration $joinAssociationDeclaration
902
     * @param int                            $joinType
903
     * @param AST\ConditionalExpression      $condExpr
904
     *
905
     * @return string
906
     *
907
     * @throws QueryException
908
     */
909 227
    public function walkJoinAssociationDeclaration($joinAssociationDeclaration, $joinType = AST\Join::JOIN_TYPE_INNER, $condExpr = null)
910
    {
911 227
        $sql = '';
912
913 227
        $associationPathExpression = $joinAssociationDeclaration->joinAssociationPathExpression;
914 227
        $joinedDqlAlias            = $joinAssociationDeclaration->aliasIdentificationVariable;
915 227
        $indexBy                   = $joinAssociationDeclaration->indexBy;
916
917 227
        $relation        = $this->queryComponents[$joinedDqlAlias]['relation'];
918 227
        $targetClass     = $this->em->getClassMetadata($relation['targetEntity']);
919 227
        $sourceClass     = $this->em->getClassMetadata($relation['sourceEntity']);
920 227
        $targetTableName = $this->quoteStrategy->getTableName($targetClass, $this->platform);
921
922 227
        $targetTableAlias = $this->getSQLTableAlias($targetClass->getTableName(), $joinedDqlAlias);
923 227
        $sourceTableAlias = $this->getSQLTableAlias($sourceClass->getTableName(), $associationPathExpression->identificationVariable);
924
925
        // Ensure we got the owning side, since it has all mapping info
926 227
        $assoc = ( ! $relation['isOwningSide']) ? $targetClass->associationMappings[$relation['mappedBy']] : $relation;
927
928 227
        if ($this->query->getHint(Query::HINT_INTERNAL_ITERATION) == true && (!$this->query->getHint(self::HINT_DISTINCT) || isset($this->selectedClasses[$joinedDqlAlias]))) {
929 4
            if ($relation['type'] == ClassMetadata::ONE_TO_MANY || $relation['type'] == ClassMetadata::MANY_TO_MANY) {
930 3
                throw QueryException::iterateWithFetchJoinNotAllowed($assoc);
931
            }
932
        }
933
934 224
        $targetTableJoin = null;
0 ignored issues
show
Unused Code introduced by
$targetTableJoin is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
935
936
        // This condition is not checking ClassMetadata::MANY_TO_ONE, because by definition it cannot
937
        // be the owning side and previously we ensured that $assoc is always the owning side of the associations.
938
        // The owning side is necessary at this point because only it contains the JoinColumn information.
939
        switch (true) {
940 224
            case ($assoc['type'] & ClassMetadata::TO_ONE):
941 177
                $conditions = array();
942
943 177
                foreach ($assoc['joinColumns'] as $joinColumn) {
944 177
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
945 177
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
946
947 177
                    if ($relation['isOwningSide']) {
948 102
                        $conditions[] = $sourceTableAlias . '.' . $quotedSourceColumn . ' = ' . $targetTableAlias . '.' . $quotedTargetColumn;
949
950 102
                        continue;
951
                    }
952
953 108
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $targetTableAlias . '.' . $quotedSourceColumn;
954
                }
955
956
                // Apply remaining inheritance restrictions
957 177
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
958
959 177
                if ($discrSql) {
960 2
                    $conditions[] = $discrSql;
961
                }
962
963
                // Apply the filters
964 177
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
965
966 177
                if ($filterExpr) {
967 1
                    $conditions[] = $filterExpr;
968
                }
969
970
                $targetTableJoin = array(
971 177
                    'table' => $targetTableName . ' ' . $targetTableAlias,
972 177
                    'condition' => implode(' AND ', $conditions),
973
                );
974 177
                break;
975
976 57
            case ($assoc['type'] == ClassMetadata::MANY_TO_MANY):
977
                // Join relation table
978 57
                $joinTable      = $assoc['joinTable'];
979 57
                $joinTableAlias = $this->getSQLTableAlias($joinTable['name'], $joinedDqlAlias);
980 57
                $joinTableName  = $this->quoteStrategy->getJoinTableName($assoc, $sourceClass, $this->platform);
981
982 57
                $conditions      = array();
983 57
                $relationColumns = ($relation['isOwningSide'])
984 48
                    ? $assoc['joinTable']['joinColumns']
985 57
                    : $assoc['joinTable']['inverseJoinColumns'];
986
987 57
                foreach ($relationColumns as $joinColumn) {
988 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
989 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
990
991 57
                    $conditions[] = $sourceTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
992
                }
993
994 57
                $sql .= $joinTableName . ' ' . $joinTableAlias . ' ON ' . implode(' AND ', $conditions);
995
996
                // Join target table
997 57
                $sql .= ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER) ? ' LEFT JOIN ' : ' INNER JOIN ';
998
999 57
                $conditions      = array();
1000 57
                $relationColumns = ($relation['isOwningSide'])
1001 48
                    ? $assoc['joinTable']['inverseJoinColumns']
1002 57
                    : $assoc['joinTable']['joinColumns'];
1003
1004 57
                foreach ($relationColumns as $joinColumn) {
1005 57
                    $quotedSourceColumn = $this->quoteStrategy->getJoinColumnName($joinColumn, $targetClass, $this->platform);
1006 57
                    $quotedTargetColumn = $this->quoteStrategy->getReferencedJoinColumnName($joinColumn, $targetClass, $this->platform);
1007
1008 57
                    $conditions[] = $targetTableAlias . '.' . $quotedTargetColumn . ' = ' . $joinTableAlias . '.' . $quotedSourceColumn;
1009
                }
1010
1011
                // Apply remaining inheritance restrictions
1012 57
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($joinedDqlAlias));
1013
1014 57
                if ($discrSql) {
1015 1
                    $conditions[] = $discrSql;
1016
                }
1017
1018
                // Apply the filters
1019 57
                $filterExpr = $this->generateFilterConditionSQL($targetClass, $targetTableAlias);
1020
1021 57
                if ($filterExpr) {
1022 1
                    $conditions[] = $filterExpr;
1023
                }
1024
1025
                $targetTableJoin = array(
1026 57
                    'table' => $targetTableName . ' ' . $targetTableAlias,
1027 57
                    'condition' => implode(' AND ', $conditions),
1028
                );
1029 57
                break;
1030
1031
            default:
1032
                throw new \BadMethodCallException('Type of association must be one of *_TO_ONE or MANY_TO_MANY');
1033
        }
1034
1035
        // Handle WITH clause
1036 224
        $withCondition = (null === $condExpr) ? '' : ('(' . $this->walkConditionalExpression($condExpr) . ')');
1037
1038 224
        if ($targetClass->isInheritanceTypeJoined()) {
1039 9
            $ctiJoins = $this->_generateClassTableInheritanceJoins($targetClass, $joinedDqlAlias);
1040
            // If we have WITH condition, we need to build nested joins for target class table and cti joins
1041 9
            if ($withCondition) {
1042 1
                $sql .= '(' . $targetTableJoin['table'] . $ctiJoins . ') ON ' . $targetTableJoin['condition'];
1043
            } else {
1044 9
                $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'] . $ctiJoins;
1045
            }
1046
        } else {
1047 215
            $sql .= $targetTableJoin['table'] . ' ON ' . $targetTableJoin['condition'];
1048
        }
1049
1050 224
        if ($withCondition) {
1051 5
            $sql .= ' AND ' . $withCondition;
1052
        }
1053
1054
        // Apply the indexes
1055 224
        if ($indexBy) {
1056
            // For Many-To-One or One-To-One associations this obviously makes no sense, but is ignored silently.
1057 5
            $this->walkIndexBy($indexBy);
1058 219
        } else if (isset($relation['indexBy'])) {
1059 3
            $this->rsm->addIndexBy($joinedDqlAlias, $relation['indexBy']);
1060
        }
1061
1062 224
        return $sql;
1063
    }
1064
1065
    /**
1066
     * {@inheritdoc}
1067
     */
1068 64
    public function walkFunction($function)
1069
    {
1070 64
        return $function->getSql($this);
1071
    }
1072
1073
    /**
1074
     * {@inheritdoc}
1075
     */
1076 153
    public function walkOrderByClause($orderByClause)
1077
    {
1078 153
        $orderByItems = array_map(array($this, 'walkOrderByItem'), $orderByClause->orderByItems);
1079
1080 152
        if (($collectionOrderByItems = $this->_generateOrderedCollectionOrderByItems()) !== '') {
1081
            $orderByItems = array_merge($orderByItems, (array) $collectionOrderByItems);
1082
        }
1083
1084 152
        return ' ORDER BY ' . implode(', ', $orderByItems);
1085
    }
1086
1087
    /**
1088
     * {@inheritdoc}
1089
     */
1090 171
    public function walkOrderByItem($orderByItem)
1091
    {
1092 171
        $type = strtoupper($orderByItem->type);
1093 171
        $expr = $orderByItem->expression;
1094 171
        $sql  = ($expr instanceof AST\Node)
1095 164
            ? $expr->dispatch($this)
1096 170
            : $this->walkResultVariable($this->queryComponents[$expr]['token']['value']);
1097
1098 170
        $this->orderedColumnsMap[$sql] = $type;
1099
1100 170
        if ($expr instanceof AST\Subselect) {
1101 2
            return '(' . $sql . ') ' . $type;
1102
        }
1103
1104 168
        return $sql . ' ' . $type;
1105
    }
1106
1107
    /**
1108
     * {@inheritdoc}
1109
     */
1110 14
    public function walkHavingClause($havingClause)
1111
    {
1112 14
        return ' HAVING ' . $this->walkConditionalExpression($havingClause->conditionalExpression);
1113
    }
1114
1115
    /**
1116
     * {@inheritdoc}
1117
     */
1118 244
    public function walkJoin($join)
1119
    {
1120 244
        $joinType        = $join->joinType;
1121 244
        $joinDeclaration = $join->joinAssociationDeclaration;
1122
1123 244
        $sql = ($joinType == AST\Join::JOIN_TYPE_LEFT || $joinType == AST\Join::JOIN_TYPE_LEFTOUTER)
1124 55
            ? ' LEFT JOIN '
1125 244
            : ' INNER JOIN ';
1126
1127
        switch (true) {
1128 244
            case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\RangeVariableDeclaration):
1129 17
                $class      = $this->em->getClassMetadata($joinDeclaration->abstractSchemaName);
1130 17
                $dqlAlias   = $joinDeclaration->aliasIdentificationVariable;
1131 17
                $tableAlias = $this->getSQLTableAlias($class->table['name'], $dqlAlias);
1132 17
                $conditions = [];
1133
1134 17
                if ($join->conditionalExpression) {
1135 15
                    $conditions[] = '(' . $this->walkConditionalExpression($join->conditionalExpression) . ')';
1136
                }
1137
1138 17
                $condExprConjunction = ($class->isInheritanceTypeJoined() && $joinType != AST\Join::JOIN_TYPE_LEFT && $joinType != AST\Join::JOIN_TYPE_LEFTOUTER)
1139 3
                    ? ' AND '
1140 17
                    : ' ON ';
1141
1142 17
                $sql .= $this->walkRangeVariableDeclaration($joinDeclaration);
1143
1144
                // Apply remaining inheritance restrictions
1145 17
                $discrSql = $this->_generateDiscriminatorColumnConditionSQL(array($dqlAlias));
1146
1147 17
                if ($discrSql) {
1148 3
                    $conditions[] = $discrSql;
1149
                }
1150
1151
                // Apply the filters
1152 17
                $filterExpr = $this->generateFilterConditionSQL($class, $tableAlias);
1153
1154 17
                if ($filterExpr) {
1155
                    $conditions[] = $filterExpr;
1156
                }
1157
1158 17
                if ($conditions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $conditions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1159 15
                    $sql .= $condExprConjunction . implode(' AND ', $conditions);
1160
                }
1161
1162 17
                break;
1163
1164
            case ($joinDeclaration instanceof \Doctrine\ORM\Query\AST\JoinAssociationDeclaration):
1165 227
                $sql .= $this->walkJoinAssociationDeclaration($joinDeclaration, $joinType, $join->conditionalExpression);
1166 224
                break;
1167
        }
1168
1169 241
        return $sql;
1170
    }
1171
1172
    /**
1173
     * Walks down a CaseExpression AST node and generates the corresponding SQL.
1174
     *
1175
     * @param AST\CoalesceExpression|AST\NullIfExpression|AST\GeneralCaseExpression|AST\SimpleCaseExpression $expression
1176
     *
1177
     * @return string The SQL.
1178
     */
1179
    public function walkCaseExpression($expression)
1180
    {
1181
        switch (true) {
1182
            case ($expression instanceof AST\CoalesceExpression):
1183
                return $this->walkCoalesceExpression($expression);
1184
1185
            case ($expression instanceof AST\NullIfExpression):
1186
                return $this->walkNullIfExpression($expression);
1187
1188
            case ($expression instanceof AST\GeneralCaseExpression):
1189
                return $this->walkGeneralCaseExpression($expression);
1190
1191
            case ($expression instanceof AST\SimpleCaseExpression):
1192
                return $this->walkSimpleCaseExpression($expression);
1193
1194
            default:
1195
                return '';
1196
        }
1197
    }
1198
1199
    /**
1200
     * Walks down a CoalesceExpression AST node and generates the corresponding SQL.
1201
     *
1202
     * @param AST\CoalesceExpression $coalesceExpression
1203
     *
1204
     * @return string The SQL.
1205
     */
1206 2
    public function walkCoalesceExpression($coalesceExpression)
1207
    {
1208 2
        $sql = 'COALESCE(';
1209
1210 2
        $scalarExpressions = array();
1211
1212 2
        foreach ($coalesceExpression->scalarExpressions as $scalarExpression) {
1213 2
            $scalarExpressions[] = $this->walkSimpleArithmeticExpression($scalarExpression);
1214
        }
1215
1216 2
        $sql .= implode(', ', $scalarExpressions) . ')';
1217
1218 2
        return $sql;
1219
    }
1220
1221
    /**
1222
     * Walks down a NullIfExpression AST node and generates the corresponding SQL.
1223
     *
1224
     * @param AST\NullIfExpression $nullIfExpression
1225
     *
1226
     * @return string The SQL.
1227
     */
1228 3
    public function walkNullIfExpression($nullIfExpression)
1229
    {
1230 3
        $firstExpression = is_string($nullIfExpression->firstExpression)
1231
            ? $this->conn->quote($nullIfExpression->firstExpression)
1232 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->firstExpression);
1233
1234 3
        $secondExpression = is_string($nullIfExpression->secondExpression)
1235
            ? $this->conn->quote($nullIfExpression->secondExpression)
1236 3
            : $this->walkSimpleArithmeticExpression($nullIfExpression->secondExpression);
1237
1238 3
        return 'NULLIF(' . $firstExpression . ', ' . $secondExpression . ')';
1239
    }
1240
1241
    /**
1242
     * Walks down a GeneralCaseExpression AST node and generates the corresponding SQL.
1243
     *
1244
     * @param AST\GeneralCaseExpression $generalCaseExpression
1245
     *
1246
     * @return string The SQL.
1247
     */
1248 9
    public function walkGeneralCaseExpression(AST\GeneralCaseExpression $generalCaseExpression)
1249
    {
1250 9
        $sql = 'CASE';
1251
1252 9
        foreach ($generalCaseExpression->whenClauses as $whenClause) {
1253 9
            $sql .= ' WHEN ' . $this->walkConditionalExpression($whenClause->caseConditionExpression);
1254 9
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($whenClause->thenScalarExpression);
1255
        }
1256
1257 9
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($generalCaseExpression->elseScalarExpression) . ' END';
1258
1259 9
        return $sql;
1260
    }
1261
1262
    /**
1263
     * Walks down a SimpleCaseExpression AST node and generates the corresponding SQL.
1264
     *
1265
     * @param AST\SimpleCaseExpression $simpleCaseExpression
1266
     *
1267
     * @return string The SQL.
1268
     */
1269 5
    public function walkSimpleCaseExpression($simpleCaseExpression)
1270
    {
1271 5
        $sql = 'CASE ' . $this->walkStateFieldPathExpression($simpleCaseExpression->caseOperand);
1272
1273 5
        foreach ($simpleCaseExpression->simpleWhenClauses as $simpleWhenClause) {
1274 5
            $sql .= ' WHEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->caseScalarExpression);
1275 5
            $sql .= ' THEN ' . $this->walkSimpleArithmeticExpression($simpleWhenClause->thenScalarExpression);
1276
        }
1277
1278 5
        $sql .= ' ELSE ' . $this->walkSimpleArithmeticExpression($simpleCaseExpression->elseScalarExpression) . ' END';
1279
1280 5
        return $sql;
1281
    }
1282
1283
    /**
1284
     * {@inheritdoc}
1285
     */
1286 618
    public function walkSelectExpression($selectExpression)
1287
    {
1288 618
        $sql    = '';
1289 618
        $expr   = $selectExpression->expression;
1290 618
        $hidden = $selectExpression->hiddenAliasResultVariable;
1291
1292
        switch (true) {
1293 618
            case ($expr instanceof AST\PathExpression):
1294 101
                if ($expr->type !== AST\PathExpression::TYPE_STATE_FIELD) {
1295
                    throw QueryException::invalidPathExpression($expr);
1296
                }
1297
1298 101
                $fieldName = $expr->field;
1299 101
                $dqlAlias  = $expr->identificationVariable;
1300 101
                $qComp     = $this->queryComponents[$dqlAlias];
1301 101
                $class     = $qComp['metadata'];
1302
1303 101
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $fieldName;
1304 101
                $tableName   = ($class->isInheritanceTypeJoined())
1305 11
                    ? $this->em->getUnitOfWork()->getEntityPersister($class->name)->getOwningTable($fieldName)
1306 101
                    : $class->getTableName();
1307
1308 101
                $sqlTableAlias = $this->getSQLTableAlias($tableName, $dqlAlias);
1309 101
                $fieldMapping  = $class->fieldMappings[$fieldName];
1310 101
                $columnName    = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1311 101
                $columnAlias   = $this->getSQLColumnAlias($fieldMapping['columnName']);
1312 101
                $col           = $sqlTableAlias . '.' . $columnName;
1313
1314 101
                if (isset($fieldMapping['requireSQLConversion'])) {
1315 2
                    $type = Type::getType($fieldMapping['type']);
1316 2
                    $col  = $type->convertToPHPValueSQL($col, $this->conn->getDatabasePlatform());
1317
                }
1318
1319 101
                $sql .= $col . ' AS ' . $columnAlias;
1320
1321 101
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1322
1323 101
                if ( ! $hidden) {
1324 101
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldMapping['type']);
1325 101
                    $this->scalarFields[$dqlAlias][$fieldName] = $columnAlias;
1326
                }
1327
1328 101
                break;
1329
1330
            case ($expr instanceof AST\AggregateExpression):
1331
            case ($expr instanceof AST\Functions\FunctionNode):
1332 10
            case ($expr instanceof AST\SimpleArithmeticExpression):
1333 22
            case ($expr instanceof AST\ArithmeticTerm):
1334 33
            case ($expr instanceof AST\ArithmeticFactor):
1335 28
            case ($expr instanceof AST\ParenthesisExpression):
1336
            case ($expr instanceof AST\Literal):
1337 37
            case ($expr instanceof AST\NullIfExpression):
1338 37
            case ($expr instanceof AST\CoalesceExpression):
1339 9
            case ($expr instanceof AST\GeneralCaseExpression):
1340 43
            case ($expr instanceof AST\SimpleCaseExpression):
1341 106
                $columnAlias = $this->getSQLColumnAlias('sclr');
1342 106
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1343
1344 106
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1345
1346 106
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1347
1348 106
                if ( ! $hidden) {
1349
                    // We cannot resolve field type here; assume 'string'.
1350 106
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1351
                }
1352 106
                break;
1353
1354
            case ($expr instanceof AST\Subselect):
1355 15
                $columnAlias = $this->getSQLColumnAlias('sclr');
1356 15
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1357
1358 15
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1359
1360 15
                $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1361
1362 15
                if ( ! $hidden) {
1363
                    // We cannot resolve field type here; assume 'string'.
1364 13
                    $this->rsm->addScalarResult($columnAlias, $resultAlias, 'string');
1365
                }
1366 15
                break;
1367
1368 482
            case ($expr instanceof AST\NewObjectExpression):
1369 22
                $sql .= $this->walkNewObject($expr,$selectExpression->fieldIdentificationVariable);
1370 22
                break;
1371
1372
            default:
1373
                // IdentificationVariable or PartialObjectExpression
1374 482
                if ($expr instanceof AST\PartialObjectExpression) {
1375 16
                    $dqlAlias = $expr->identificationVariable;
1376 16
                    $partialFieldSet = $expr->partialFieldSet;
1377
                } else {
1378 477
                    $dqlAlias = $expr;
1379 477
                    $partialFieldSet = array();
1380
                }
1381
1382 482
                $queryComp   = $this->queryComponents[$dqlAlias];
1383 482
                $class       = $queryComp['metadata'];
1384 482
                $resultAlias = $selectExpression->fieldIdentificationVariable ?: null;
1385
1386 482
                if ( ! isset($this->selectedClasses[$dqlAlias])) {
1387 482
                    $this->selectedClasses[$dqlAlias] = array(
1388 482
                        'class'       => $class,
1389 482
                        'dqlAlias'    => $dqlAlias,
1390 482
                        'resultAlias' => $resultAlias
1391
                    );
1392
                }
1393
1394 482
                $sqlParts = array();
1395
1396
                // Select all fields from the queried class
1397 482
                foreach ($class->fieldMappings as $fieldName => $mapping) {
1398 481
                    if ($partialFieldSet && ! in_array($fieldName, $partialFieldSet)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $partialFieldSet of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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

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

Loading history...
1399 14
                        continue;
1400
                    }
1401
1402 480
                    $tableName = (isset($mapping['inherited']))
1403 51
                        ? $this->em->getClassMetadata($mapping['inherited'])->getTableName()
1404 480
                        : $class->getTableName();
1405
1406 480
                    $sqlTableAlias    = $this->getSQLTableAlias($tableName, $dqlAlias);
1407 480
                    $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1408 480
                    $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $class, $this->platform);
1409
1410 480
                    $col = $sqlTableAlias . '.' . $quotedColumnName;
1411
1412 480
                    if (isset($mapping['requireSQLConversion'])) {
1413 5
                        $type = Type::getType($mapping['type']);
1414 5
                        $col = $type->convertToPHPValueSQL($col, $this->platform);
1415
                    }
1416
1417 480
                    $sqlParts[] = $col . ' AS '. $columnAlias;
1418
1419 480
                    $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1420
1421 480
                    $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 482
                if ($class->isInheritanceTypeSingleTable() || ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD)) {
1429 388
                    foreach ($class->subClasses as $subClassName) {
1430 40
                        $subClass      = $this->em->getClassMetadata($subClassName);
1431 40
                        $sqlTableAlias = $this->getSQLTableAlias($subClass->getTableName(), $dqlAlias);
1432
1433 40
                        foreach ($subClass->fieldMappings as $fieldName => $mapping) {
1434 40
                            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 40
                                continue;
1436
                            }
1437
1438 34
                            $columnAlias      = $this->getSQLColumnAlias($mapping['columnName']);
1439 34
                            $quotedColumnName = $this->quoteStrategy->getColumnName($fieldName, $subClass, $this->platform);
1440
1441 34
                            $col = $sqlTableAlias . '.' . $quotedColumnName;
1442
1443 34
                            if (isset($mapping['requireSQLConversion'])) {
1444
                                $type = Type::getType($mapping['type']);
1445
                                $col = $type->convertToPHPValueSQL($col, $this->platform);
1446
                            }
1447
1448 34
                            $sqlParts[] = $col . ' AS ' . $columnAlias;
1449
1450 34
                            $this->scalarResultAliasMap[$resultAlias][] = $columnAlias;
1451
1452 40
                            $this->rsm->addFieldResult($dqlAlias, $columnAlias, $fieldName, $subClassName);
1453
                        }
1454
                    }
1455
                }
1456
1457 482
                $sql .= implode(', ', $sqlParts);
1458
        }
1459
1460 618
        return $sql;
1461
    }
1462
1463
    /**
1464
     * {@inheritdoc}
1465
     */
1466
    public function walkQuantifiedExpression($qExpr)
1467
    {
1468
        return ' ' . strtoupper($qExpr->type) . '(' . $this->walkSubselect($qExpr->subselect) . ')';
1469
    }
1470
1471
    /**
1472
     * {@inheritdoc}
1473
     */
1474 33
    public function walkSubselect($subselect)
1475
    {
1476 33
        $useAliasesBefore  = $this->useSqlTableAliases;
1477 33
        $rootAliasesBefore = $this->rootAliases;
1478
1479 33
        $this->rootAliases = array(); // reset the rootAliases for the subselect
1480 33
        $this->useSqlTableAliases = true;
1481
1482 33
        $sql  = $this->walkSimpleSelectClause($subselect->simpleSelectClause);
1483 33
        $sql .= $this->walkSubselectFromClause($subselect->subselectFromClause);
1484 33
        $sql .= $this->walkWhereClause($subselect->whereClause);
0 ignored issues
show
Bug introduced by
It seems like $subselect->whereClause can be null; however, walkWhereClause() does not accept null, maybe add an additional type check?

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1485
1486 33
        $sql .= $subselect->groupByClause ? $this->walkGroupByClause($subselect->groupByClause) : '';
1487 33
        $sql .= $subselect->havingClause ? $this->walkHavingClause($subselect->havingClause) : '';
1488 33
        $sql .= $subselect->orderByClause ? $this->walkOrderByClause($subselect->orderByClause) : '';
1489
1490 33
        $this->rootAliases        = $rootAliasesBefore; // put the main aliases back
1491 33
        $this->useSqlTableAliases = $useAliasesBefore;
1492
1493 33
        return $sql;
1494
    }
1495
1496
    /**
1497
     * {@inheritdoc}
1498
     */
1499 33
    public function walkSubselectFromClause($subselectFromClause)
1500
    {
1501 33
        $identificationVarDecls = $subselectFromClause->identificationVariableDeclarations;
1502 33
        $sqlParts               = array ();
1503
1504 33
        foreach ($identificationVarDecls as $subselectIdVarDecl) {
1505 33
            $sqlParts[] = $this->walkIdentificationVariableDeclaration($subselectIdVarDecl);
1506
        }
1507
1508 33
        return ' FROM ' . implode(', ', $sqlParts);
1509
    }
1510
1511
    /**
1512
     * {@inheritdoc}
1513
     */
1514 33
    public function walkSimpleSelectClause($simpleSelectClause)
1515
    {
1516 33
        return 'SELECT' . ($simpleSelectClause->isDistinct ? ' DISTINCT' : '')
1517 33
            . $this->walkSimpleSelectExpression($simpleSelectClause->simpleSelectExpression);
1518
    }
1519
1520
    /**
1521
     * @param \Doctrine\ORM\Query\AST\ParenthesisExpression $parenthesisExpression
1522
     *
1523
     * @return string.
0 ignored issues
show
Documentation introduced by
The doc-type string. could not be parsed: Unknown type name "string." at position 0. (view supported doc-types)

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

Loading history...
1524
     */
1525 22
    public function walkParenthesisExpression(AST\ParenthesisExpression $parenthesisExpression)
1526
    {
1527 22
        return sprintf('(%s)', $parenthesisExpression->expression->dispatch($this));
1528
    }
1529
1530
    /**
1531
     * @param AST\NewObjectExpression $newObjectExpression
1532
     *
1533
     * @return string The SQL.
1534
     */
1535 22
    public function walkNewObject($newObjectExpression, $newObjectResultAlias=null)
1536
    {
1537 22
        $sqlSelectExpressions = array();
1538 22
        $objIndex             = $newObjectResultAlias?:$this->newObjectCounter++;
1539
1540 22
        foreach ($newObjectExpression->args as $argIndex => $e) {
1541 22
            $resultAlias = $this->scalarResultCounter++;
1542 22
            $columnAlias = $this->getSQLColumnAlias('sclr');
1543 22
            $fieldType   = 'string';
1544
1545
            switch (true) {
1546 22
                case ($e instanceof AST\NewObjectExpression):
1547
                    $sqlSelectExpressions[] = $e->dispatch($this);
1548
                    break;
1549
1550
                case ($e instanceof AST\Subselect):
1551 1
                    $sqlSelectExpressions[] = '(' . $e->dispatch($this) . ') AS ' . $columnAlias;
1552 1
                    break;
1553
1554
                case ($e instanceof AST\PathExpression):
1555 22
                    $dqlAlias  = $e->identificationVariable;
1556 22
                    $qComp     = $this->queryComponents[$dqlAlias];
1557 22
                    $class     = $qComp['metadata'];
1558 22
                    $fieldType = $class->fieldMappings[$e->field]['type'];
1559
1560 22
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1561 22
                    break;
1562
1563 5
                case ($e instanceof AST\Literal):
1564 1
                    switch ($e->type) {
1565 1
                        case AST\Literal::BOOLEAN:
1566 1
                            $fieldType = 'boolean';
1567 1
                            break;
1568
1569 1
                        case AST\Literal::NUMERIC:
1570 1
                            $fieldType = is_float($e->value) ? 'float' : 'integer';
1571 1
                            break;
1572
                    }
1573
1574 1
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1575 1
                    break;
1576
1577
                default:
1578 5
                    $sqlSelectExpressions[] = trim($e->dispatch($this)) . ' AS ' . $columnAlias;
1579 5
                    break;
1580
            }
1581
1582 22
            $this->scalarResultAliasMap[$resultAlias] = $columnAlias;
1583 22
            $this->rsm->addScalarResult($columnAlias, $resultAlias, $fieldType);
1584
1585 22
            $this->rsm->newObjectMappings[$columnAlias] = array(
1586 22
                'className' => $newObjectExpression->className,
1587 22
                'objIndex'  => $objIndex,
1588 22
                'argIndex'  => $argIndex
1589
            );
1590
        }
1591
1592 22
        return implode(', ', $sqlSelectExpressions);
1593
    }
1594
1595
    /**
1596
     * {@inheritdoc}
1597
     */
1598 33
    public function walkSimpleSelectExpression($simpleSelectExpression)
1599
    {
1600 33
        $expr = $simpleSelectExpression->expression;
1601 33
        $sql  = ' ';
1602
1603
        switch (true) {
1604 33
            case ($expr instanceof AST\PathExpression):
1605 9
                $sql .= $this->walkPathExpression($expr);
1606 9
                break;
1607
1608
            case ($expr instanceof AST\AggregateExpression):
1609 14
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1610
1611 14
                $sql .= $this->walkAggregateExpression($expr) . ' AS dctrn__' . $alias;
1612 14
                break;
1613
1614
            case ($expr instanceof AST\Subselect):
1615
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1616
1617
                $columnAlias = 'sclr' . $this->aliasCounter++;
1618
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1619
1620
                $sql .= '(' . $this->walkSubselect($expr) . ') AS ' . $columnAlias;
1621
                break;
1622
1623
            case ($expr instanceof AST\Functions\FunctionNode):
1624
            case ($expr instanceof AST\SimpleArithmeticExpression):
1625
            case ($expr instanceof AST\ArithmeticTerm):
1626
            case ($expr instanceof AST\ArithmeticFactor):
1627
            case ($expr instanceof AST\Literal):
1628
            case ($expr instanceof AST\NullIfExpression):
1629
            case ($expr instanceof AST\CoalesceExpression):
1630
            case ($expr instanceof AST\GeneralCaseExpression):
1631
            case ($expr instanceof AST\SimpleCaseExpression):
1632 8
                $alias = $simpleSelectExpression->fieldIdentificationVariable ?: $this->scalarResultCounter++;
1633
1634 8
                $columnAlias = $this->getSQLColumnAlias('sclr');
1635 8
                $this->scalarResultAliasMap[$alias] = $columnAlias;
1636
1637 8
                $sql .= $expr->dispatch($this) . ' AS ' . $columnAlias;
1638 8
                break;
1639
1640 2
            case ($expr instanceof AST\ParenthesisExpression):
1641 1
                $sql .= $this->walkParenthesisExpression($expr);
1642 1
                break;
1643
1644
            default: // IdentificationVariable
1645 2
                $sql .= $this->walkEntityIdentificationVariable($expr);
1646 2
                break;
1647
        }
1648
1649 33
        return $sql;
1650
    }
1651
1652
    /**
1653
     * {@inheritdoc}
1654
     */
1655 75
    public function walkAggregateExpression($aggExpression)
1656
    {
1657 75
        return $aggExpression->functionName . '(' . ($aggExpression->isDistinct ? 'DISTINCT ' : '')
1658 75
            . $this->walkSimpleArithmeticExpression($aggExpression->pathExpression) . ')';
0 ignored issues
show
Bug introduced by
It seems like $aggExpression->pathExpression can also be of type object<Doctrine\ORM\Query\AST\PathExpression>; however, Doctrine\ORM\Query\SqlWa...eArithmeticExpression() does only seem to accept object<Doctrine\ORM\Quer...leArithmeticExpression>, maybe add an additional type check?

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

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

    return array();
}

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

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

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

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

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

function doesNotAcceptNull(stdClass $x) { }

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

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

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

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

Let’s take a look at an example:

class Author {
    private $name;

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

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

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

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

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

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

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

Loading history...
2123
2124 105
            case AST\Literal::NUMERIC:
2125 105
                return $literal->value;
2126
2127
            default:
2128
                throw QueryException::invalidLiteral($literal);
2129
        }
2130
    }
2131
2132
    /**
2133
     * {@inheritdoc}
2134
     */
2135 6
    public function walkBetweenExpression($betweenExpr)
2136
    {
2137 6
        $sql = $this->walkArithmeticExpression($betweenExpr->expression);
2138
2139 6
        if ($betweenExpr->not) {
2140 2
            $sql .= ' NOT';
2141
        }
2142
2143 6
        $sql .= ' BETWEEN ' . $this->walkArithmeticExpression($betweenExpr->leftBetweenExpression)
2144 6
            . ' AND ' . $this->walkArithmeticExpression($betweenExpr->rightBetweenExpression);
2145
2146 6
        return $sql;
2147
    }
2148
2149
    /**
2150
     * {@inheritdoc}
2151
     */
2152 9
    public function walkLikeExpression($likeExpr)
2153
    {
2154 9
        $stringExpr = $likeExpr->stringExpression;
2155 9
        $leftExpr   = (is_string($stringExpr) && isset($this->queryComponents[$stringExpr]['resultVariable']))
2156 1
            ? $this->walkResultVariable($stringExpr)
2157 9
            : $stringExpr->dispatch($this);
2158
2159 9
        $sql = $leftExpr . ($likeExpr->not ? ' NOT' : '') . ' LIKE ';
2160
2161 9
        if ($likeExpr->stringPattern instanceof AST\InputParameter) {
2162 4
            $sql .= $this->walkInputParameter($likeExpr->stringPattern);
2163 6
        } elseif ($likeExpr->stringPattern instanceof AST\Functions\FunctionNode) {
2164 2
            $sql .= $this->walkFunction($likeExpr->stringPattern);
2165 6
        } elseif ($likeExpr->stringPattern instanceof AST\PathExpression) {
2166 2
            $sql .= $this->walkPathExpression($likeExpr->stringPattern);
2167
        } else {
2168 6
            $sql .= $this->walkLiteral($likeExpr->stringPattern);
2169
        }
2170
2171 9
        if ($likeExpr->escapeChar) {
2172 1
            $sql .= ' ESCAPE ' . $this->walkLiteral($likeExpr->escapeChar);
2173
        }
2174
2175 9
        return $sql;
2176
    }
2177
2178
    /**
2179
     * {@inheritdoc}
2180
     */
2181 5
    public function walkStateFieldPathExpression($stateFieldPathExpression)
2182
    {
2183 5
        return $this->walkPathExpression($stateFieldPathExpression);
2184
    }
2185
2186
    /**
2187
     * {@inheritdoc}
2188
     */
2189 258
    public function walkComparisonExpression($compExpr)
2190
    {
2191 258
        $leftExpr  = $compExpr->leftExpression;
2192 258
        $rightExpr = $compExpr->rightExpression;
2193 258
        $sql       = '';
2194
2195 258
        $sql .= ($leftExpr instanceof AST\Node)
2196 258
            ? $leftExpr->dispatch($this)
2197 257
            : (is_numeric($leftExpr) ? $leftExpr : $this->conn->quote($leftExpr));
2198
2199 257
        $sql .= ' ' . $compExpr->operator . ' ';
2200
2201 257
        $sql .= ($rightExpr instanceof AST\Node)
2202 255
            ? $rightExpr->dispatch($this)
2203 257
            : (is_numeric($rightExpr) ? $rightExpr : $this->conn->quote($rightExpr));
2204
2205 257
        return $sql;
2206
    }
2207
2208
    /**
2209
     * {@inheritdoc}
2210
     */
2211 212
    public function walkInputParameter($inputParam)
2212
    {
2213 212
        $this->parserResult->addParameterMapping($inputParam->name, $this->sqlParamIndex++);
2214
2215 212
        $parameter = $this->query->getParameter($inputParam->name);
2216
2217 212
        if ($parameter && Type::hasType($type = $parameter->getType())) {
2218 52
            return Type::getType($type)->convertToDatabaseValueSQL('?', $this->platform);
2219
        }
2220
2221 166
        return '?';
2222
    }
2223
2224
    /**
2225
     * {@inheritdoc}
2226
     */
2227 324
    public function walkArithmeticExpression($arithmeticExpr)
2228
    {
2229 324
        return ($arithmeticExpr->isSimpleArithmeticExpression())
2230 324
            ? $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...
2231 322
            : '(' . $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...
2232
    }
2233
2234
    /**
2235
     * {@inheritdoc}
2236
     */
2237 386
    public function walkSimpleArithmeticExpression($simpleArithmeticExpr)
2238
    {
2239 386
        if ( ! ($simpleArithmeticExpr instanceof AST\SimpleArithmeticExpression)) {
2240 336
            return $this->walkArithmeticTerm($simpleArithmeticExpr);
2241
        }
2242
2243 75
        return implode(' ', array_map(array($this, 'walkArithmeticTerm'), $simpleArithmeticExpr->arithmeticTerms));
2244
    }
2245
2246
    /**
2247
     * {@inheritdoc}
2248
     */
2249 407
    public function walkArithmeticTerm($term)
2250
    {
2251 407
        if (is_string($term)) {
2252 21
            return (isset($this->queryComponents[$term]))
2253 6
                ? $this->walkResultVariable($this->queryComponents[$term]['token']['value'])
2254 21
                : $term;
2255
        }
2256
2257
        // Phase 2 AST optimization: Skip processing of ArithmeticTerm
2258
        // if only one ArithmeticFactor is defined
2259 406
        if ( ! ($term instanceof AST\ArithmeticTerm)) {
2260 384
            return $this->walkArithmeticFactor($term);
2261
        }
2262
2263 47
        return implode(' ', array_map(array($this, 'walkArithmeticFactor'), $term->arithmeticFactors));
2264
    }
2265
2266
    /**
2267
     * {@inheritdoc}
2268
     */
2269 407
    public function walkArithmeticFactor($factor)
2270
    {
2271 407
        if (is_string($factor)) {
2272 47
            return (isset($this->queryComponents[$factor]))
2273 2
                ? $this->walkResultVariable($this->queryComponents[$factor]['token']['value'])
2274 47
                : $factor;
2275
        }
2276
2277
        // Phase 2 AST optimization: Skip processing of ArithmeticFactor
2278
        // if only one ArithmeticPrimary is defined
2279 407
        if ( ! ($factor instanceof AST\ArithmeticFactor)) {
2280 406
            return $this->walkArithmeticPrimary($factor);
2281
        }
2282
2283 3
        $sign = $factor->isNegativeSigned() ? '-' : ($factor->isPositiveSigned() ? '+' : '');
2284
2285 3
        return $sign . $this->walkArithmeticPrimary($factor->arithmeticPrimary);
2286
    }
2287
2288
    /**
2289
     * Walks down an ArithmeticPrimary that represents an AST node, thereby generating the appropriate SQL.
2290
     *
2291
     * @param mixed $primary
2292
     *
2293
     * @return string The SQL.
2294
     */
2295 407
    public function walkArithmeticPrimary($primary)
2296
    {
2297 407
        if ($primary instanceof AST\SimpleArithmeticExpression) {
2298
            return '(' . $this->walkSimpleArithmeticExpression($primary) . ')';
2299
        }
2300
2301 407
        if ($primary instanceof AST\Node) {
2302 407
            return $primary->dispatch($this);
2303
        }
2304
2305
        return $this->walkEntityIdentificationVariable($primary);
2306
    }
2307
2308
    /**
2309
     * {@inheritdoc}
2310
     */
2311 18
    public function walkStringPrimary($stringPrimary)
2312
    {
2313 18
        return (is_string($stringPrimary))
2314
            ? $this->conn->quote($stringPrimary)
2315 18
            : $stringPrimary->dispatch($this);
2316
    }
2317
2318
    /**
2319
     * {@inheritdoc}
2320
     */
2321 30
    public function walkResultVariable($resultVariable)
2322
    {
2323 30
        $resultAlias = $this->scalarResultAliasMap[$resultVariable];
2324
2325 30
        if (is_array($resultAlias)) {
2326 1
            return implode(', ', $resultAlias);
2327
        }
2328
2329 29
        return $resultAlias;
2330
    }
2331
}
2332