Completed
Push — master ( 89a008...28025b )
by Marco
09:44 queued 19s
created

SqlWalker::walkStateFieldPathExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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