source: pro-violet-viettel/sourcecode/application/libraries/Doctrine/ORM/Internal/Hydration/AbstractHydrator.php @ 360

Last change on this file since 360 was 345, checked in by quyenla, 11 years ago

collaborator page

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