Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / orm / lib / Doctrine / ORM / Internal / Hydration / AbstractHydrator.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,
23     Doctrine\DBAL\Connection,
24     Doctrine\DBAL\Types\Type,
25     Doctrine\ORM\EntityManager,
26     Doctrine\ORM\Events,
27     Doctrine\ORM\Mapping\ClassMetadata;
28
29 /**
30  * Base class for all hydrators. A hydrator is a class that provides some form
31  * of transformation of an SQL result set into another structure.
32  *
33  * @since  2.0
34  * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
35  * @author Roman Borschel <roman@code-factory.org>
36  * @author Guilherme Blanco <guilhermeblanoc@hotmail.com>
37  */
38 abstract class AbstractHydrator
39 {
40     /** @var \Doctrine\ORM\Query\ResultSetMapping The ResultSetMapping. */
41     protected $_rsm;
42
43     /** @var EntityManager The EntityManager instance. */
44     protected $_em;
45
46     /** @var \Doctrine\DBAL\Platforms\AbstractPlatform The dbms Platform instance */
47     protected $_platform;
48
49     /** @var \Doctrine\ORM\UnitOfWork The UnitOfWork of the associated EntityManager. */
50     protected $_uow;
51
52     /** @var array The cache used during row-by-row hydration. */
53     protected $_cache = array();
54
55     /** @var \Doctrine\DBAL\Driver\Statement The statement that provides the data to hydrate. */
56     protected $_stmt;
57
58     /** @var array The query hints. */
59     protected $_hints;
60
61     /**
62      * Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>.
63      *
64      * @param \Doctrine\ORM\EntityManager $em The EntityManager to use.
65      */
66     public function __construct(EntityManager $em)
67     {
68         $this->_em       = $em;
69         $this->_platform = $em->getConnection()->getDatabasePlatform();
70         $this->_uow      = $em->getUnitOfWork();
71     }
72
73     /**
74      * Initiates a row-by-row hydration.
75      *
76      * @param object $stmt
77      * @param object $resultSetMapping
78      *
79      * @return IterableResult
80      */
81     public function iterate($stmt, $resultSetMapping, array $hints = array())
82     {
83         $this->_stmt  = $stmt;
84         $this->_rsm   = $resultSetMapping;
85         $this->_hints = $hints;
86
87         $evm = $this->_em->getEventManager();
88         $evm->addEventListener(array(Events::onClear), $this);
89
90         $this->prepare();
91
92         return new IterableResult($this);
93     }
94
95     /**
96      * Hydrates all rows returned by the passed statement instance at once.
97      *
98      * @param object $stmt
99      * @param object $resultSetMapping
100      * @param array $hints
101      * @return mixed
102      */
103     public function hydrateAll($stmt, $resultSetMapping, array $hints = array())
104     {
105         $this->_stmt  = $stmt;
106         $this->_rsm   = $resultSetMapping;
107         $this->_hints = $hints;
108
109         $this->prepare();
110
111         $result = $this->hydrateAllData();
112
113         $this->cleanup();
114
115         return $result;
116     }
117
118     /**
119      * Hydrates a single row returned by the current statement instance during
120      * row-by-row hydration with {@link iterate()}.
121      *
122      * @return mixed
123      */
124     public function hydrateRow()
125     {
126         $row = $this->_stmt->fetch(PDO::FETCH_ASSOC);
127
128         if ( ! $row) {
129             $this->cleanup();
130
131             return false;
132         }
133
134         $result = array();
135
136         $this->hydrateRowData($row, $this->_cache, $result);
137
138         return $result;
139     }
140
141     /**
142      * Excutes one-time preparation tasks, once each time hydration is started
143      * through {@link hydrateAll} or {@link iterate()}.
144      */
145     protected function prepare()
146     {}
147
148     /**
149      * Excutes one-time cleanup tasks at the end of a hydration that was initiated
150      * through {@link hydrateAll} or {@link iterate()}.
151      */
152     protected function cleanup()
153     {
154         $this->_rsm = null;
155
156         $this->_stmt->closeCursor();
157         $this->_stmt = null;
158     }
159
160     /**
161      * Hydrates a single row from the current statement instance.
162      *
163      * Template method.
164      *
165      * @param array $data The row data.
166      * @param array $cache The cache to use.
167      * @param mixed $result The result to fill.
168      */
169     protected function hydrateRowData(array $data, array &$cache, array &$result)
170     {
171         throw new HydrationException("hydrateRowData() not implemented by this hydrator.");
172     }
173
174     /**
175      * Hydrates all rows from the current statement instance at once.
176      */
177     abstract protected function hydrateAllData();
178
179     /**
180      * Processes a row of the result set.
181      *
182      * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY).
183      * Puts the elements of a result row into a new array, grouped by the dql alias
184      * they belong to. The column names in the result set are mapped to their
185      * field names during this procedure as well as any necessary conversions on
186      * the values applied. Scalar values are kept in a specfic key 'scalars'.
187      *
188      * @param array $data SQL Result Row
189      * @param array &$cache Cache for column to field result information
190      * @param array &$id Dql-Alias => ID-Hash
191      * @param array &$nonemptyComponents Does this DQL-Alias has at least one non NULL value?
192      *
193      * @return array  An array with all the fields (name => value) of the data row,
194      *                grouped by their component alias.
195      */
196     protected function gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents)
197     {
198         $rowData = array();
199
200         foreach ($data as $key => $value) {
201             // Parse each column name only once. Cache the results.
202             if ( ! isset($cache[$key])) {
203                 switch (true) {
204                     // NOTE: Most of the times it's a field mapping, so keep it first!!!
205                     case (isset($this->_rsm->fieldMappings[$key])):
206                         $fieldName     = $this->_rsm->fieldMappings[$key];
207                         $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
208
209                         $cache[$key]['fieldName']    = $fieldName;
210                         $cache[$key]['type']         = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
211                         $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName);
212                         $cache[$key]['dqlAlias']     = $this->_rsm->columnOwnerMap[$key];
213                         break;
214
215                     case (isset($this->_rsm->scalarMappings[$key])):
216                         $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
217                         $cache[$key]['type']      = Type::getType($this->_rsm->typeMappings[$key]);
218                         $cache[$key]['isScalar']  = true;
219                         break;
220
221                     case (isset($this->_rsm->metaMappings[$key])):
222                         // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
223                         $fieldName     = $this->_rsm->metaMappings[$key];
224                         $classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$this->_rsm->columnOwnerMap[$key]]);
225
226                         $cache[$key]['isMetaColumn'] = true;
227                         $cache[$key]['fieldName']    = $fieldName;
228                         $cache[$key]['dqlAlias']     = $this->_rsm->columnOwnerMap[$key];
229                         $cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]);
230                         break;
231
232                     default:
233                         // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
234                         // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
235                         continue 2;
236                 }
237             }
238
239             if (isset($cache[$key]['isScalar'])) {
240                 $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
241
242                 $rowData['scalars'][$cache[$key]['fieldName']] = $value;
243
244                 continue;
245             }
246
247             $dqlAlias = $cache[$key]['dqlAlias'];
248
249             if ($cache[$key]['isIdentifier']) {
250                 $id[$dqlAlias] .= '|' . $value;
251             }
252
253             if (isset($cache[$key]['isMetaColumn'])) {
254                 if ( ! isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value !== null) {
255                     $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value;
256                     if ($cache[$key]['isIdentifier']) {
257                         $nonemptyComponents[$dqlAlias] = true;
258                     }
259                 }
260
261                 continue;
262             }
263
264             // in an inheritance hierarchy the same field could be defined several times.
265             // We overwrite this value so long we dont have a non-null value, that value we keep.
266             // Per definition it cannot be that a field is defined several times and has several values.
267             if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) {
268                 continue;
269             }
270
271             $rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
272
273             if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) {
274                 $nonemptyComponents[$dqlAlias] = true;
275             }
276         }
277
278         return $rowData;
279     }
280
281     /**
282      * Processes a row of the result set.
283      *
284      * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that
285      * simply converts column names to field names and properly converts the
286      * values according to their types. The resulting row has the same number
287      * of elements as before.
288      *
289      * @param array $data
290      * @param array $cache
291      *
292      * @return array The processed row.
293      */
294     protected function gatherScalarRowData(&$data, &$cache)
295     {
296         $rowData = array();
297
298         foreach ($data as $key => $value) {
299             // Parse each column name only once. Cache the results.
300             if ( ! isset($cache[$key])) {
301                 switch (true) {
302                     // NOTE: During scalar hydration, most of the times it's a scalar mapping, keep it first!!!
303                     case (isset($this->_rsm->scalarMappings[$key])):
304                         $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key];
305                         $cache[$key]['isScalar']  = true;
306                         break;
307
308                     case (isset($this->_rsm->fieldMappings[$key])):
309                         $fieldName     = $this->_rsm->fieldMappings[$key];
310                         $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]);
311
312                         $cache[$key]['fieldName'] = $fieldName;
313                         $cache[$key]['type']      = Type::getType($classMetadata->fieldMappings[$fieldName]['type']);
314                         $cache[$key]['dqlAlias']  = $this->_rsm->columnOwnerMap[$key];
315                         break;
316
317                     case (isset($this->_rsm->metaMappings[$key])):
318                         // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns).
319                         $cache[$key]['isMetaColumn'] = true;
320                         $cache[$key]['fieldName']    = $this->_rsm->metaMappings[$key];
321                         $cache[$key]['dqlAlias']     = $this->_rsm->columnOwnerMap[$key];
322                         break;
323
324                     default:
325                         // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2
326                         // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping.
327                         continue 2;
328                 }
329             }
330
331             $fieldName = $cache[$key]['fieldName'];
332
333             switch (true) {
334                 case (isset($cache[$key]['isScalar'])):
335                     $rowData[$fieldName] = $value;
336                     break;
337
338                 case (isset($cache[$key]['isMetaColumn'])):
339                     $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
340                     break;
341
342                 default:
343                     $value = $cache[$key]['type']->convertToPHPValue($value, $this->_platform);
344
345                     $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value;
346             }
347         }
348
349         return $rowData;
350     }
351
352     /**
353      * Register entity as managed in UnitOfWork.
354      *
355      * @param \Doctrine\ORM\Mapping\ClassMetadata $class
356      * @param object $entity
357      * @param array $data
358      *
359      * @todo The "$id" generation is the same of UnitOfWork#createEntity. Remove this duplication somehow
360      */
361     protected function registerManaged(ClassMetadata $class, $entity, array $data)
362     {
363         if ($class->isIdentifierComposite) {
364             $id = array();
365             foreach ($class->identifier as $fieldName) {
366                 if (isset($class->associationMappings[$fieldName])) {
367                     $id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']];
368                 } else {
369                     $id[$fieldName] = $data[$fieldName];
370                 }
371             }
372         } else {
373             if (isset($class->associationMappings[$class->identifier[0]])) {
374                 $id = array($class->identifier[0] => $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]);
375             } else {
376                 $id = array($class->identifier[0] => $data[$class->identifier[0]]);
377             }
378         }
379
380         $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data);
381     }
382
383     /**
384      * When executed in a hydrate() loop we have to clear internal state to
385      * decrease memory consumption.
386      */
387     public function onClear($eventArgs)
388     {
389     }
390 }