Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / orm / lib / Doctrine / ORM / Internal / Hydration / ArrayHydrator.php
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\Internal\Hydration;
21
22 use PDO, Doctrine\DBAL\Connection, Doctrine\ORM\Mapping\ClassMetadata;
23
24 /**
25  * The ArrayHydrator produces a nested array "graph" that is often (not always)
26  * interchangeable with the corresponding object graph for read-only access.
27  *
28  * @since  2.0
29  * @author Roman Borschel <roman@code-factory.org>
30  * @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
31  */
32 class ArrayHydrator extends AbstractHydrator
33 {
34     private $_ce = array();
35     private $_rootAliases = array();
36     private $_isSimpleQuery = false;
37     private $_identifierMap = array();
38     private $_resultPointers = array();
39     private $_idTemplate = array();
40     private $_resultCounter = 0;
41
42     /**
43      * {@inheritdoc}
44      */
45     protected function prepare()
46     {
47         $this->_isSimpleQuery  = count($this->_rsm->aliasMap) <= 1;
48         $this->_identifierMap  = array();
49         $this->_resultPointers = array();
50         $this->_idTemplate     = array();
51         $this->_resultCounter  = 0;
52
53         foreach ($this->_rsm->aliasMap as $dqlAlias => $className) {
54             $this->_identifierMap[$dqlAlias]  = array();
55             $this->_resultPointers[$dqlAlias] = array();
56             $this->_idTemplate[$dqlAlias]     = '';
57         }
58     }
59
60     /**
61      * {@inheritdoc}
62      */
63     protected function hydrateAllData()
64     {
65         $result = array();
66         $cache  = array();
67
68         while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
69             $this->hydrateRowData($data, $cache, $result);
70         }
71
72         return $result;
73     }
74
75     /**
76      * {@inheritdoc}
77      */
78     protected function hydrateRowData(array $row, array &$cache, array &$result)
79     {
80         // 1) Initialize
81         $id = $this->_idTemplate; // initialize the id-memory
82         $nonemptyComponents = array();
83         $rowData = $this->gatherRowData($row, $cache, $id, $nonemptyComponents);
84
85         // Extract scalar values. They're appended at the end.
86         if (isset($rowData['scalars'])) {
87             $scalars = $rowData['scalars'];
88
89             unset($rowData['scalars']);
90
91             if (empty($rowData)) {
92                 ++$this->_resultCounter;
93             }
94         }
95
96         // 2) Now hydrate the data found in the current row.
97         foreach ($rowData as $dqlAlias => $data) {
98             $index = false;
99
100             if (isset($this->_rsm->parentAliasMap[$dqlAlias])) {
101                 // It's a joined result
102
103                 $parent = $this->_rsm->parentAliasMap[$dqlAlias];
104                 $path   = $parent . '.' . $dqlAlias;
105
106                 // missing parent data, skipping as RIGHT JOIN hydration is not supported.
107                 if ( ! isset($nonemptyComponents[$parent]) ) {
108                     continue;
109                 }
110
111                 // Get a reference to the right element in the result tree.
112                 // This element will get the associated element attached.
113                 if ($this->_rsm->isMixed && isset($this->_rootAliases[$parent])) {
114                     $first = reset($this->_resultPointers);
115                     // TODO: Exception if $key === null ?
116                     $baseElement =& $this->_resultPointers[$parent][key($first)];
117                 } else if (isset($this->_resultPointers[$parent])) {
118                     $baseElement =& $this->_resultPointers[$parent];
119                 } else {
120                     unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
121                     continue;
122                 }
123
124                 $relationAlias = $this->_rsm->relationMap[$dqlAlias];
125                 $relation = $this->getClassMetadata($this->_rsm->aliasMap[$parent])->associationMappings[$relationAlias];
126
127                 // Check the type of the relation (many or single-valued)
128                 if ( ! ($relation['type'] & ClassMetadata::TO_ONE)) {
129                     $oneToOne = false;
130
131                     if (isset($nonemptyComponents[$dqlAlias])) {
132                         if ( ! isset($baseElement[$relationAlias])) {
133                             $baseElement[$relationAlias] = array();
134                         }
135
136                         $indexExists  = isset($this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]]);
137                         $index        = $indexExists ? $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] : false;
138                         $indexIsValid = $index !== false ? isset($baseElement[$relationAlias][$index]) : false;
139
140                         if ( ! $indexExists || ! $indexIsValid) {
141                             $element = $data;
142                             if (isset($this->_rsm->indexByMap[$dqlAlias])) {
143                                 $baseElement[$relationAlias][$row[$this->_rsm->indexByMap[$dqlAlias]]] = $element;
144                             } else {
145                                 $baseElement[$relationAlias][] = $element;
146                             }
147
148                             end($baseElement[$relationAlias]);
149
150                             $this->_identifierMap[$path][$id[$parent]][$id[$dqlAlias]] = key($baseElement[$relationAlias]);
151                         }
152                     } else if ( ! isset($baseElement[$relationAlias])) {
153                         $baseElement[$relationAlias] = array();
154                     }
155                 } else {
156                     $oneToOne = true;
157
158                     if ( ! isset($nonemptyComponents[$dqlAlias]) && ! isset($baseElement[$relationAlias])) {
159                         $baseElement[$relationAlias] = null;
160                     } else if ( ! isset($baseElement[$relationAlias])) {
161                         $baseElement[$relationAlias] = $data;
162                     }
163                 }
164
165                 $coll =& $baseElement[$relationAlias];
166
167                 if ($coll !== null) {
168                     $this->updateResultPointer($coll, $index, $dqlAlias, $oneToOne);
169                 }
170
171             } else {
172                 // It's a root result element
173
174                 $this->_rootAliases[$dqlAlias] = true; // Mark as root
175                 $entityKey = $this->_rsm->entityMappings[$dqlAlias] ?: 0;
176
177                 // if this row has a NULL value for the root result id then make it a null result.
178                 if ( ! isset($nonemptyComponents[$dqlAlias]) ) {
179                     if ($this->_rsm->isMixed) {
180                         $result[] = array($entityKey => null);
181                     } else {
182                         $result[] = null;
183                     }
184                     $resultKey = $this->_resultCounter;
185                     ++$this->_resultCounter;
186                     continue;
187                 }
188
189                 // Check for an existing element
190                 if ($this->_isSimpleQuery || ! isset($this->_identifierMap[$dqlAlias][$id[$dqlAlias]])) {
191                     $element = $rowData[$dqlAlias];
192                     if ($this->_rsm->isMixed) {
193                         $element = array($entityKey => $element);
194                     }
195
196                     if (isset($this->_rsm->indexByMap[$dqlAlias])) {
197                         $resultKey = $row[$this->_rsm->indexByMap[$dqlAlias]];
198                         $result[$resultKey] = $element;
199                     } else {
200                         $resultKey = $this->_resultCounter;
201                         $result[] = $element;
202                         ++$this->_resultCounter;
203                     }
204
205                     $this->_identifierMap[$dqlAlias][$id[$dqlAlias]] = $resultKey;
206                 } else {
207                     $index = $this->_identifierMap[$dqlAlias][$id[$dqlAlias]];
208                     $resultKey = $index;
209                     /*if ($this->_rsm->isMixed) {
210                         $result[] =& $result[$index];
211                         ++$this->_resultCounter;
212                     }*/
213                 }
214                 $this->updateResultPointer($result, $index, $dqlAlias, false);
215             }
216         }
217
218         // Append scalar values to mixed result sets
219         if (isset($scalars)) {
220             if ( ! isset($resultKey) ) {
221                 // this only ever happens when no object is fetched (scalar result only)
222                 if (isset($this->_rsm->indexByMap['scalars'])) {
223                     $resultKey = $row[$this->_rsm->indexByMap['scalars']];
224                 } else {
225                     $resultKey = $this->_resultCounter - 1;
226                 }
227             }
228
229             foreach ($scalars as $name => $value) {
230                 $result[$resultKey][$name] = $value;
231             }
232         }
233     }
234
235     /**
236      * Updates the result pointer for an Entity. The result pointers point to the
237      * last seen instance of each Entity type. This is used for graph construction.
238      *
239      * @param array $coll  The element.
240      * @param boolean|integer $index  Index of the element in the collection.
241      * @param string $dqlAlias
242      * @param boolean $oneToOne  Whether it is a single-valued association or not.
243      */
244     private function updateResultPointer(array &$coll, $index, $dqlAlias, $oneToOne)
245     {
246         if ($coll === null) {
247             unset($this->_resultPointers[$dqlAlias]); // Ticket #1228
248
249             return;
250         }
251
252         if ($index !== false) {
253             $this->_resultPointers[$dqlAlias] =& $coll[$index];
254
255             return;
256         }
257
258         if ( ! $coll) {
259             return;
260         }
261
262         if ($oneToOne) {
263             $this->_resultPointers[$dqlAlias] =& $coll;
264
265             return;
266         }
267
268         end($coll);
269         $this->_resultPointers[$dqlAlias] =& $coll[key($coll)];
270
271         return;
272     }
273
274     /**
275      * Retrieve ClassMetadata associated to entity class name.
276      *
277      * @param string $className
278      *
279      * @return \Doctrine\ORM\Mapping\ClassMetadata
280      */
281     private function getClassMetadata($className)
282     {
283         if ( ! isset($this->_ce[$className])) {
284             $this->_ce[$className] = $this->_em->getClassMetadata($className);
285         }
286
287         return $this->_ce[$className];
288     }
289 }