Failed Conditions
Push — master ( 6744b4...2b8acb )
by Marco
60:45 queued 60:36
created

lib/Doctrine/ORM/Query/ResultSetMappingBuilder.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\ORM\EntityManagerInterface;
23
use Doctrine\ORM\Mapping\ClassMetadataInfo;
24
use Doctrine\ORM\Mapping\MappingException;
25
use Doctrine\ORM\Utility\PersisterHelper;
26
27
/**
28
 * A ResultSetMappingBuilder uses the EntityManager to automatically populate entity fields.
29
 *
30
 * @author Michael Ridgway <[email protected]>
31
 * @since 2.1
32
 */
33
class ResultSetMappingBuilder extends ResultSetMapping
34
{
35
    /**
36
     * Picking this rename mode will register entity columns as is,
37
     * as they are in the database. This can cause clashes when multiple
38
     * entities are fetched that have columns with the same name.
39
     *
40
     * @var int
41
     */
42
    const COLUMN_RENAMING_NONE = 1;
43
44
    /**
45
     * Picking custom renaming allows the user to define the renaming
46
     * of specific columns with a rename array that contains column names as
47
     * keys and result alias as values.
48
     *
49
     * @var int
50
     */
51
    const COLUMN_RENAMING_CUSTOM = 2;
52
53
    /**
54
     * Incremental renaming uses a result set mapping internal counter to add a
55
     * number to each column result, leading to uniqueness. This only works if
56
     * you use {@see generateSelectClause()} to generate the SELECT clause for
57
     * you.
58
     *
59
     * @var int
60
     */
61
    const COLUMN_RENAMING_INCREMENT = 3;
62
63
    /**
64
     * @var int
65
     */
66
    private $sqlCounter = 0;
67
68
    /**
69
     * @var EntityManagerInterface
70
     */
71
    private $em;
72
73
    /**
74
     * Default column renaming mode.
75
     *
76
     * @var int
77
     */
78
    private $defaultRenameMode;
79
80
    /**
81
     * @param EntityManagerInterface $em
82
     * @param integer                $defaultRenameMode
83
     */
84 58
    public function __construct(EntityManagerInterface $em, $defaultRenameMode = self::COLUMN_RENAMING_NONE)
85
    {
86 58
        $this->em                = $em;
87 58
        $this->defaultRenameMode = $defaultRenameMode;
88 58
    }
89
90
    /**
91
     * Adds a root entity and all of its fields to the result set.
92
     *
93
     * @param string   $class          The class name of the root entity.
94
     * @param string   $alias          The unique alias to use for the root entity.
95
     * @param array    $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
96
     * @param int|null $renameMode     One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM).
97
     *
98
     * @return void
99
     */
100 39 View Code Duplication
    public function addRootEntityFromClassMetadata($class, $alias, $renamedColumns = [], $renameMode = null)
101
    {
102 39
        $renameMode     = $renameMode ?: $this->defaultRenameMode;
103 39
        $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns);
104
105 39
        $this->addEntityResult($class, $alias);
106 39
        $this->addAllClassFields($class, $alias, $columnAliasMap);
107 38
    }
108
109
    /**
110
     * Adds a joined entity and all of its fields to the result set.
111
     *
112
     * @param string   $class          The class name of the joined entity.
113
     * @param string   $alias          The unique alias to use for the joined entity.
114
     * @param string   $parentAlias    The alias of the entity result that is the parent of this joined result.
115
     * @param string   $relation       The association field that connects the parent entity result
116
     *                                 with the joined entity result.
117
     * @param array    $renamedColumns Columns that have been renamed (tableColumnName => queryColumnName).
118
     * @param int|null $renameMode     One of the COLUMN_RENAMING_* constants or array for BC reasons (CUSTOM).
119
     *
120
     * @return void
121
     */
122 12 View Code Duplication
    public function addJoinedEntityFromClassMetadata($class, $alias, $parentAlias, $relation, $renamedColumns = [], $renameMode = null)
123
    {
124 12
        $renameMode     = $renameMode ?: $this->defaultRenameMode;
125 12
        $columnAliasMap = $this->getColumnAliasMap($class, $renameMode, $renamedColumns);
126
127 12
        $this->addJoinedEntityResult($class, $alias, $parentAlias, $relation);
128 12
        $this->addAllClassFields($class, $alias, $columnAliasMap);
129 11
    }
130
131
    /**
132
     * Adds all fields of the given class to the result set mapping (columns and meta fields).
133
     *
134
     * @param string $class
135
     * @param string $alias
136
     * @param array  $columnAliasMap
137
     *
138
     * @return void
139
     *
140
     * @throws \InvalidArgumentException
141
     */
142 39
    protected function addAllClassFields($class, $alias, $columnAliasMap = [])
143
    {
144 39
        $classMetadata = $this->em->getClassMetadata($class);
145 39
        $platform      = $this->em->getConnection()->getDatabasePlatform();
146
147 39
        if ( ! $this->isInheritanceSupported($classMetadata)) {
148 1
            throw new \InvalidArgumentException('ResultSetMapping builder does not currently support your inheritance scheme.');
149
        }
150
151
152 38
        foreach ($classMetadata->getColumnNames() as $columnName) {
153 38
            $propertyName = $classMetadata->getFieldName($columnName);
0 ignored issues
show
The method getFieldName() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean getFieldNames()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
154 38
            $columnAlias  = $platform->getSQLResultCasing($columnAliasMap[$columnName]);
155
156 38
            if (isset($this->fieldMappings[$columnAlias])) {
157 1
                throw new \InvalidArgumentException("The column '$columnName' conflicts with another column in the mapper.");
158
            }
159
160 38
            $this->addFieldResult($alias, $columnAlias, $propertyName);
161
        }
162
163 38
        foreach ($classMetadata->associationMappings as $associationMapping) {
164 28
            if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
165 22
                $targetClass  = $this->em->getClassMetadata($associationMapping['targetEntity']);
166 22
                $isIdentifier = isset($associationMapping['id']) && $associationMapping['id'] === true;
167
168 22
                foreach ($associationMapping['joinColumns'] as $joinColumn) {
169 22
                    $columnName  = $joinColumn['name'];
170 22
                    $columnAlias = $platform->getSQLResultCasing($columnAliasMap[$columnName]);
171 22
                    $columnType = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
172
173 22
                    if (isset($this->metaMappings[$columnAlias])) {
174
                        throw new \InvalidArgumentException("The column '$columnAlias' conflicts with another column in the mapper.");
175
                    }
176
177 28
                    $this->addMetaResult($alias, $columnAlias, $columnName, $isIdentifier, $columnType);
178
                }
179
            }
180
        }
181 38
    }
182
183 39
    private function isInheritanceSupported(ClassMetadataInfo $classMetadata)
184
    {
185 39
        if ($classMetadata->isInheritanceTypeSingleTable()
186 39
            && in_array($classMetadata->name, $classMetadata->discriminatorMap, true)) {
187 3
            return true;
188
        }
189
190 37
        return ! ($classMetadata->isInheritanceTypeSingleTable() || $classMetadata->isInheritanceTypeJoined());
191
    }
192
193
    /**
194
     * Gets column alias for a given column.
195
     *
196
     * @param string $columnName
197
     * @param int    $mode
198
     * @param array  $customRenameColumns
199
     *
200
     * @return string
201
     */
202 39
    private function getColumnAlias($columnName, $mode, array $customRenameColumns)
203
    {
204
        switch ($mode) {
205 39
            case self::COLUMN_RENAMING_INCREMENT:
206 3
                return $columnName . $this->sqlCounter++;
207
208 36
            case self::COLUMN_RENAMING_CUSTOM:
209 11
                return isset($customRenameColumns[$columnName])
210 11
                    ? $customRenameColumns[$columnName] : $columnName;
211
212 35
            case self::COLUMN_RENAMING_NONE:
213 35
                return $columnName;
214
215
        }
216
    }
217
218
    /**
219
     * Retrieves a class columns and join columns aliases that are used in the SELECT clause.
220
     *
221
     * This depends on the renaming mode selected by the user.
222
     *
223
     * @param string $className
224
     * @param int    $mode
225
     * @param array  $customRenameColumns
226
     *
227
     * @return array
228
     */
229 39
    private function getColumnAliasMap($className, $mode, array $customRenameColumns)
230
    {
231 39
        if ($customRenameColumns) { // for BC with 2.2-2.3 API
232 11
            $mode = self::COLUMN_RENAMING_CUSTOM;
233
        }
234
235 39
        $columnAlias = [];
236 39
        $class       = $this->em->getClassMetadata($className);
237
238 39
        foreach ($class->getColumnNames() as $columnName) {
239 39
            $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns);
240
        }
241
242 39
        foreach ($class->associationMappings as $associationMapping) {
243 29
            if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
244 23
                foreach ($associationMapping['joinColumns'] as $joinColumn) {
245 23
                    $columnName = $joinColumn['name'];
246 29
                    $columnAlias[$columnName] = $this->getColumnAlias($columnName, $mode, $customRenameColumns);
247
                }
248
            }
249
        }
250
251 39
        return $columnAlias;
252
    }
253
254
    /**
255
     * Adds the mappings of the results of native SQL queries to the result set.
256
     *
257
     * @param ClassMetadataInfo $class
258
     * @param array             $queryMapping
259
     *
260
     * @return ResultSetMappingBuilder
261
     */
262 10
    public function addNamedNativeQueryMapping(ClassMetadataInfo $class, array $queryMapping)
263
    {
264 10
        if (isset($queryMapping['resultClass'])) {
265 3
            return $this->addNamedNativeQueryResultClassMapping($class, $queryMapping['resultClass']);
266
        }
267
268 8
        return $this->addNamedNativeQueryResultSetMapping($class, $queryMapping['resultSetMapping']);
269
    }
270
271
    /**
272
     * Adds the class mapping of the results of native SQL queries to the result set.
273
     *
274
     * @param ClassMetadataInfo $class
275
     * @param string            $resultClassName
276
     *
277
     * @return  ResultSetMappingBuilder
278
     */
279 3
    public function addNamedNativeQueryResultClassMapping(ClassMetadataInfo $class, $resultClassName)
280
    {
281 3
        $classMetadata  = $this->em->getClassMetadata($resultClassName);
282 3
        $shortName      = $classMetadata->reflClass->getShortName();
283 3
        $alias          = strtolower($shortName[0]).'0';
284
285 3
        $this->addEntityResult($class->name, $alias);
286
287 3
        if ($classMetadata->discriminatorColumn) {
288 1
            $discrColumn = $classMetadata->discriminatorColumn;
289
290 1
            $this->setDiscriminatorColumn($alias, $discrColumn['name']);
291 1
            $this->addMetaResult($alias, $discrColumn['name'], $discrColumn['fieldName'], false, $discrColumn['type']);
292
        }
293
294 3
        foreach ($classMetadata->getColumnNames() as $key => $columnName) {
295 3
            $propertyName = $classMetadata->getFieldName($columnName);
296
297 3
            $this->addFieldResult($alias, $columnName, $propertyName);
298
        }
299
300 3
        foreach ($classMetadata->associationMappings as $associationMapping) {
301 3
            if ($associationMapping['isOwningSide'] && $associationMapping['type'] & ClassMetadataInfo::TO_ONE) {
302 3
                $targetClass = $this->em->getClassMetadata($associationMapping['targetEntity']);
303
304 3
                foreach ($associationMapping['joinColumns'] as $joinColumn) {
305 3
                    $columnName  = $joinColumn['name'];
306 3
                    $columnType  = PersisterHelper::getTypeOfColumn($joinColumn['referencedColumnName'], $targetClass, $this->em);
307
308 3
                    $this->addMetaResult($alias, $columnName, $columnName, $classMetadata->isIdentifier($columnName), $columnType);
309
                }
310
            }
311
        }
312
313 3
        return $this;
314
    }
315
316
    /**
317
     * Adds the result set mapping of the results of native SQL queries to the result set.
318
     *
319
     * @param ClassMetadataInfo $class
320
     * @param string            $resultSetMappingName
321
     *
322
     * @return ResultSetMappingBuilder
323
     */
324 8
    public function addNamedNativeQueryResultSetMapping(ClassMetadataInfo $class, $resultSetMappingName)
325
    {
326 8
        $counter        = 0;
327 8
        $resultMapping  = $class->getSqlResultSetMapping($resultSetMappingName);
328 8
        $rootShortName  = $class->reflClass->getShortName();
329 8
        $rootAlias      = strtolower($rootShortName[0]) . $counter;
330
331
332 8
        if (isset($resultMapping['entities'])) {
333 8
            foreach ($resultMapping['entities'] as $key => $entityMapping) {
334 8
                $classMetadata  = $this->em->getClassMetadata($entityMapping['entityClass']);
335
336 8
                if ($class->reflClass->name == $classMetadata->reflClass->name) {
337 8
                    $this->addEntityResult($classMetadata->name, $rootAlias);
338 8
                    $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $rootAlias);
339
                } else {
340 2
                    $shortName      = $classMetadata->reflClass->getShortName();
341 2
                    $joinAlias      = strtolower($shortName[0]) . ++ $counter;
342 2
                    $associations   = $class->getAssociationsByTargetClass($classMetadata->name);
343
344 2
                    $this->addNamedNativeQueryEntityResultMapping($classMetadata, $entityMapping, $joinAlias);
345
346 2
                    foreach ($associations as $relation => $mapping) {
347 8
                        $this->addJoinedEntityResult($mapping['targetEntity'], $joinAlias, $rootAlias, $relation);
348
                    }
349
                }
350
351
            }
352
        }
353
354 8
        if (isset($resultMapping['columns'])) {
355 8
            foreach ($resultMapping['columns'] as $entityMapping) {
356 4
                $type = isset($class->fieldNames[$entityMapping['name']])
357
                    ? PersisterHelper::getTypeOfColumn($entityMapping['name'], $class, $this->em)
358 4
                    : 'string';
359
360 4
                $this->addScalarResult($entityMapping['name'], $entityMapping['name'], $type);
361
            }
362
        }
363
364 8
        return $this;
365
    }
366
367
    /**
368
     * Adds the entity result mapping of the results of native SQL queries to the result set.
369
     *
370
     * @param ClassMetadataInfo $classMetadata
371
     * @param array             $entityMapping
372
     * @param string            $alias
373
     *
374
     * @return ResultSetMappingBuilder
375
     *
376
     * @throws MappingException
377
     * @throws \InvalidArgumentException
378
     */
379 8
    public function addNamedNativeQueryEntityResultMapping(ClassMetadataInfo $classMetadata, array $entityMapping, $alias)
380
    {
381 8
        if (isset($entityMapping['discriminatorColumn']) && $entityMapping['discriminatorColumn']) {
382 1
            $discriminatorColumn = $entityMapping['discriminatorColumn'];
383 1
            $discriminatorType   = $classMetadata->discriminatorColumn['type'];
384
385 1
            $this->setDiscriminatorColumn($alias, $discriminatorColumn);
386 1
            $this->addMetaResult($alias, $discriminatorColumn, $discriminatorColumn, false, $discriminatorType);
387
        }
388
389 8
        if (isset($entityMapping['fields']) && !empty($entityMapping['fields'])) {
390 7
            foreach ($entityMapping['fields'] as $field) {
391 7
                $fieldName = $field['name'];
392 7
                $relation  = null;
393
394 7
                if (strpos($fieldName, '.') !== false) {
395 2
                    list($relation, $fieldName) = explode('.', $fieldName);
396
                }
397
398 7
                if (isset($classMetadata->associationMappings[$relation])) {
399 2
                    if ($relation) {
400 2
                        $associationMapping = $classMetadata->associationMappings[$relation];
401 2
                        $joinAlias          = $alias.$relation;
402 2
                        $parentAlias        = $alias;
403
404 2
                        $this->addJoinedEntityResult($associationMapping['targetEntity'], $joinAlias, $parentAlias, $relation);
405 2
                        $this->addFieldResult($joinAlias, $field['column'], $fieldName);
406
                    } else {
407 2
                        $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name);
408
                    }
409
                } else {
410 7
                    if ( ! isset($classMetadata->fieldMappings[$fieldName])) {
411
                        throw new \InvalidArgumentException("Entity '".$classMetadata->name."' has no field '".$fieldName."'. ");
412
                    }
413
414 7
                    $this->addFieldResult($alias, $field['column'], $fieldName, $classMetadata->name);
415
                }
416
            }
417
418
        } else {
419 1
            foreach ($classMetadata->getColumnNames() as $columnName) {
420 1
                $propertyName = $classMetadata->getFieldName($columnName);
421
422 1
                $this->addFieldResult($alias, $columnName, $propertyName);
423
            }
424
        }
425
426 8
        return $this;
427
    }
428
429
    /**
430
     * Generates the Select clause from this ResultSetMappingBuilder.
431
     *
432
     * Works only for all the entity results. The select parts for scalar
433
     * expressions have to be written manually.
434
     *
435
     * @param array $tableAliases
436
     *
437
     * @return string
438
     */
439 13
    public function generateSelectClause($tableAliases = [])
440
    {
441 13
        $sql = "";
442
443 13
        foreach ($this->columnOwnerMap as $columnName => $dqlAlias) {
444 13
            $tableAlias = isset($tableAliases[$dqlAlias])
445 13
                ? $tableAliases[$dqlAlias] : $dqlAlias;
446
447 13
            if ($sql) {
448 13
                $sql .= ", ";
449
            }
450
451 13
            $sql .= $tableAlias . ".";
452
453 13
            if (isset($this->fieldMappings[$columnName])) {
454 13
                $class = $this->em->getClassMetadata($this->declaringClasses[$columnName]);
455 13
                $sql  .= $class->fieldMappings[$this->fieldMappings[$columnName]]['columnName'];
456 7
            } else if (isset($this->metaMappings[$columnName])) {
457 6
                $sql .= $this->metaMappings[$columnName];
458 1
            } else if (isset($this->discriminatorColumns[$dqlAlias])) {
459 1
                $sql .= $this->discriminatorColumns[$dqlAlias];
460
            }
461
462 13
            $sql .= " AS " . $columnName;
463
        }
464
465 13
        return $sql;
466
    }
467
468
    /**
469
     * @return string
470
     */
471 1
    public function __toString()
472
    {
473 1
        return $this->generateSelectClause([]);
474
    }
475
}
476