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
20namespace Doctrine\ORM\Query;
21
22/**
23 * A ResultSetMapping describes how a result set of an SQL query maps to a Doctrine result.
24 *
25 * IMPORTANT NOTE:
26 * The properties of this class are only public for fast internal READ access and to (drastically)
27 * reduce the size of serialized instances for more effective caching due to better (un-)serialization
28 * performance.
29 *
30 * <b>Users should use the public methods.</b>
31 *
32 * @author Roman Borschel <roman@code-factory.org>
33 * @since 2.0
34 * @todo Think about whether the number of lookup maps can be reduced.
35 */
36class ResultSetMapping
37{
38    /**
39     * Whether the result is mixed (contains scalar values together with field values).
40     *
41     * @ignore
42     * @var boolean
43     */
44    public $isMixed = false;
45
46    /**
47     * Whether the result is a select statement.
48     *
49     * @ignore
50     * @var boolean
51     */
52    public $isSelect = true;
53
54    /**
55     * Maps alias names to class names.
56     *
57     * @ignore
58     * @var array
59     */
60    public $aliasMap = array();
61
62    /**
63     * Maps alias names to related association field names.
64     *
65     * @ignore
66     * @var array
67     */
68    public $relationMap = array();
69
70    /**
71     * Maps alias names to parent alias names.
72     *
73     * @ignore
74     * @var array
75     */
76    public $parentAliasMap = array();
77
78    /**
79     * Maps column names in the result set to field names for each class.
80     *
81     * @ignore
82     * @var array
83     */
84    public $fieldMappings = array();
85
86    /**
87     * Maps column names in the result set to the alias/field name to use in the mapped result.
88     *
89     * @ignore
90     * @var array
91     */
92    public $scalarMappings = array();
93
94    /**
95     * Maps column names in the result set to the alias/field type to use in the mapped result.
96     *
97     * @ignore
98     * @var array
99     */
100    public $typeMappings = array();
101
102    /**
103     * Maps entities in the result set to the alias name to use in the mapped result.
104     *
105     * @ignore
106     * @var array
107     */
108    public $entityMappings = array();
109
110    /**
111     * Maps column names of meta columns (foreign keys, discriminator columns, ...) to field names.
112     *
113     * @ignore
114     * @var array
115     */
116    public $metaMappings = array();
117
118    /**
119     * Maps column names in the result set to the alias they belong to.
120     *
121     * @ignore
122     * @var array
123     */
124    public $columnOwnerMap = array();
125
126    /**
127     * List of columns in the result set that are used as discriminator columns.
128     *
129     * @ignore
130     * @var array
131     */
132    public $discriminatorColumns = array();
133
134    /**
135     * Maps alias names to field names that should be used for indexing.
136     *
137     * @ignore
138     * @var array
139     */
140    public $indexByMap = array();
141
142    /**
143     * Map from column names to class names that declare the field the column is mapped to.
144     *
145     * @ignore
146     * @var array
147     */
148    public $declaringClasses = array();
149
150    /**
151     * This is necessary to hydrate derivate foreign keys correctly.
152     *
153     * @var array
154     */
155    public $isIdentifierColumn = array();
156
157    /**
158     * Maps column names in the result set to field names for each new object expression.
159     *
160     * @var array
161     */
162    public $newObjectMappings = array();
163
164    /**
165     * Maps metadata parameter names to the metadata attribute.
166     *
167     * @var array
168     */
169    public $metadataParameterMapping = array();
170
171    /**
172     * Adds an entity result to this ResultSetMapping.
173     *
174     * @param string $class            The class name of the entity.
175     * @param string $alias            The alias for the class. The alias must be unique among all entity
176     *                                 results or joined entity results within this ResultSetMapping.
177     * @param string|null $resultAlias The result alias with which the entity result should be
178     *                                 placed in the result structure.
179     *
180     * @return ResultSetMapping This ResultSetMapping instance.
181     *
182     * @todo Rename: addRootEntity
183     */
184    public function addEntityResult($class, $alias, $resultAlias = null)
185    {
186        $this->aliasMap[$alias] = $class;
187        $this->entityMappings[$alias] = $resultAlias;
188
189        if ($resultAlias !== null) {
190            $this->isMixed = true;
191        }
192
193        return $this;
194    }
195
196    /**
197     * Sets a discriminator column for an entity result or joined entity result.
198     * The discriminator column will be used to determine the concrete class name to
199     * instantiate.
200     *
201     * @param string $alias       The alias of the entity result or joined entity result the discriminator
202     *                            column should be used for.
203     * @param string $discrColumn The name of the discriminator column in the SQL result set.
204     *
205     * @return ResultSetMapping This ResultSetMapping instance.
206     *
207     * @todo Rename: addDiscriminatorColumn
208     */
209    public function setDiscriminatorColumn($alias, $discrColumn)
210    {
211        $this->discriminatorColumns[$alias] = $discrColumn;
212        $this->columnOwnerMap[$discrColumn] = $alias;
213
214        return $this;
215    }
216
217    /**
218     * Sets a field to use for indexing an entity result or joined entity result.
219     *
220     * @param string $alias     The alias of an entity result or joined entity result.
221     * @param string $fieldName The name of the field to use for indexing.
222     *
223     * @return ResultSetMapping This ResultSetMapping instance.
224     */
225    public function addIndexBy($alias, $fieldName)
226    {
227        $found = false;
228
229        foreach (array_merge($this->metaMappings, $this->fieldMappings) as $columnName => $columnFieldName) {
230            if ( ! ($columnFieldName === $fieldName && $this->columnOwnerMap[$columnName] === $alias)) continue;
231
232            $this->addIndexByColumn($alias, $columnName);
233            $found = true;
234
235            break;
236        }
237
238        /* TODO: check if this exception can be put back, for now it's gone because of assumptions made by some ORM internals
239        if ( ! $found) {
240            $message = sprintf(
241                'Cannot add index by for DQL alias %s and field %s without calling addFieldResult() for them before.',
242                $alias,
243                $fieldName
244            );
245
246            throw new \LogicException($message);
247        }
248        */
249
250        return $this;
251    }
252
253    /**
254     * Sets to index by a scalar result column name.
255     *
256     * @param string $resultColumnName
257     *
258     * @return ResultSetMapping This ResultSetMapping instance.
259     */
260    public function addIndexByScalar($resultColumnName)
261    {
262        $this->indexByMap['scalars'] = $resultColumnName;
263
264        return $this;
265    }
266
267    /**
268     * Sets a column to use for indexing an entity or joined entity result by the given alias name.
269     *
270     * @param string $alias
271     * @param string $resultColumnName
272     *
273     * @return ResultSetMapping This ResultSetMapping instance.
274     */
275    public function addIndexByColumn($alias, $resultColumnName)
276    {
277        $this->indexByMap[$alias] = $resultColumnName;
278
279        return $this;
280    }
281
282    /**
283     * Checks whether an entity result or joined entity result with a given alias has
284     * a field set for indexing.
285     *
286     * @param string $alias
287     *
288     * @return boolean
289     *
290     * @todo Rename: isIndexed($alias)
291     */
292    public function hasIndexBy($alias)
293    {
294        return isset($this->indexByMap[$alias]);
295    }
296
297    /**
298     * Checks whether the column with the given name is mapped as a field result
299     * as part of an entity result or joined entity result.
300     *
301     * @param string $columnName The name of the column in the SQL result set.
302     *
303     * @return boolean
304     *
305     * @todo Rename: isField
306     */
307    public function isFieldResult($columnName)
308    {
309        return isset($this->fieldMappings[$columnName]);
310    }
311
312    /**
313     * Adds a field to the result that belongs to an entity or joined entity.
314     *
315     * @param string      $alias          The alias of the root entity or joined entity to which the field belongs.
316     * @param string      $columnName     The name of the column in the SQL result set.
317     * @param string      $fieldName      The name of the field on the declaring class.
318     * @param string|null $declaringClass The name of the class that declares/owns the specified field.
319     *                                    When $alias refers to a superclass in a mapped hierarchy but
320     *                                    the field $fieldName is defined on a subclass, specify that here.
321     *                                    If not specified, the field is assumed to belong to the class
322     *                                    designated by $alias.
323     *
324     * @return ResultSetMapping This ResultSetMapping instance.
325     *
326     * @todo Rename: addField
327     */
328    public function addFieldResult($alias, $columnName, $fieldName, $declaringClass = null)
329    {
330        // column name (in result set) => field name
331        $this->fieldMappings[$columnName] = $fieldName;
332        // column name => alias of owner
333        $this->columnOwnerMap[$columnName] = $alias;
334        // field name => class name of declaring class
335        $this->declaringClasses[$columnName] = $declaringClass ?: $this->aliasMap[$alias];
336
337        if ( ! $this->isMixed && $this->scalarMappings) {
338            $this->isMixed = true;
339        }
340
341        return $this;
342    }
343
344    /**
345     * Adds a joined entity result.
346     *
347     * @param string $class       The class name of the joined entity.
348     * @param string $alias       The unique alias to use for the joined entity.
349     * @param string $parentAlias The alias of the entity result that is the parent of this joined result.
350     * @param string $relation    The association field that connects the parent entity result
351     *                            with the joined entity result.
352     *
353     * @return ResultSetMapping This ResultSetMapping instance.
354     *
355     * @todo Rename: addJoinedEntity
356     */
357    public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
358    {
359        $this->aliasMap[$alias]       = $class;
360        $this->parentAliasMap[$alias] = $parentAlias;
361        $this->relationMap[$alias]    = $relation;
362
363        return $this;
364    }
365
366    /**
367     * Adds a scalar result mapping.
368     *
369     * @param string $columnName The name of the column in the SQL result set.
370     * @param string $alias      The result alias with which the scalar result should be placed in the result structure.
371     * @param string $type       The column type
372     *
373     * @return ResultSetMapping This ResultSetMapping instance.
374     *
375     * @todo Rename: addScalar
376     */
377    public function addScalarResult($columnName, $alias, $type = 'string')
378    {
379        $this->scalarMappings[$columnName] = $alias;
380        $this->typeMappings[$columnName]   = $type;
381
382        if ( ! $this->isMixed && $this->fieldMappings) {
383            $this->isMixed = true;
384        }
385
386        return $this;
387    }
388
389    /**
390     * Adds a metadata parameter mappings.
391     *
392     * @param mixed $parameter      The parameter name in the SQL result set.
393     * @param string $attribute     The metadata attribute.
394     */
395    public function addMetadataParameterMapping($parameter, $attribute)
396    {
397        $this->metadataParameterMapping[$parameter] = $attribute;
398    }
399
400    /**
401     * Checks whether a column with a given name is mapped as a scalar result.
402     *
403     * @param string $columnName The name of the column in the SQL result set.
404     *
405     * @return boolean
406     *
407     * @todo Rename: isScalar
408     */
409    public function isScalarResult($columnName)
410    {
411        return isset($this->scalarMappings[$columnName]);
412    }
413
414    /**
415     * Gets the name of the class of an entity result or joined entity result,
416     * identified by the given unique alias.
417     *
418     * @param string $alias
419     *
420     * @return string
421     */
422    public function getClassName($alias)
423    {
424        return $this->aliasMap[$alias];
425    }
426
427    /**
428     * Gets the field alias for a column that is mapped as a scalar value.
429     *
430     * @param string $columnName The name of the column in the SQL result set.
431     *
432     * @return string
433     */
434    public function getScalarAlias($columnName)
435    {
436        return $this->scalarMappings[$columnName];
437    }
438
439    /**
440     * Gets the name of the class that owns a field mapping for the specified column.
441     *
442     * @param string $columnName
443     *
444     * @return string
445     */
446    public function getDeclaringClass($columnName)
447    {
448        return $this->declaringClasses[$columnName];
449    }
450
451    /**
452     * @param string $alias
453     *
454     * @return AssociationMapping
455     */
456    public function getRelation($alias)
457    {
458        return $this->relationMap[$alias];
459    }
460
461    /**
462     * @param string $alias
463     *
464     * @return boolean
465     */
466    public function isRelation($alias)
467    {
468        return isset($this->relationMap[$alias]);
469    }
470
471    /**
472     * Gets the alias of the class that owns a field mapping for the specified column.
473     *
474     * @param string $columnName
475     *
476     * @return string
477     */
478    public function getEntityAlias($columnName)
479    {
480        return $this->columnOwnerMap[$columnName];
481    }
482
483    /**
484     * Gets the parent alias of the given alias.
485     *
486     * @param string $alias
487     *
488     * @return string
489     */
490    public function getParentAlias($alias)
491    {
492        return $this->parentAliasMap[$alias];
493    }
494
495    /**
496     * Checks whether the given alias has a parent alias.
497     *
498     * @param string $alias
499     *
500     * @return boolean
501     */
502    public function hasParentAlias($alias)
503    {
504        return isset($this->parentAliasMap[$alias]);
505    }
506
507    /**
508     * Gets the field name for a column name.
509     *
510     * @param string $columnName
511     *
512     * @return string
513     */
514    public function getFieldName($columnName)
515    {
516        return $this->fieldMappings[$columnName];
517    }
518
519    /**
520     * @return array
521     */
522    public function getAliasMap()
523    {
524        return $this->aliasMap;
525    }
526
527    /**
528     * Gets the number of different entities that appear in the mapped result.
529     *
530     * @return integer
531     */
532    public function getEntityResultCount()
533    {
534        return count($this->aliasMap);
535    }
536
537    /**
538     * Checks whether this ResultSetMapping defines a mixed result.
539     *
540     * Mixed results can only occur in object and array (graph) hydration. In such a
541     * case a mixed result means that scalar values are mixed with objects/array in
542     * the result.
543     *
544     * @return boolean
545     */
546    public function isMixedResult()
547    {
548        return $this->isMixed;
549    }
550
551    /**
552     * Adds a meta column (foreign key or discriminator column) to the result set.
553     *
554     * @param string $alias                 The result alias with which the meta result should be placed in the result structure.
555     * @param string $columnName            The name of the column in the SQL result set.
556     * @param string $fieldName             The name of the field on the declaring class.
557     * @param bool   $isIdentifierColumn
558     * @param string $type                  The column type
559     *
560     * @return ResultSetMapping This ResultSetMapping instance.
561     */
562    public function addMetaResult($alias, $columnName, $fieldName, $isIdentifierColumn = false, $type = null)
563    {
564        $this->metaMappings[$columnName] = $fieldName;
565        $this->columnOwnerMap[$columnName] = $alias;
566
567        if ($isIdentifierColumn) {
568            $this->isIdentifierColumn[$alias][$columnName] = true;
569        }
570
571        if ($type) {
572            $this->typeMappings[$columnName] = $type;
573        }
574
575        return $this;
576    }
577}
578
579