|
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 |
|
20 namespace Doctrine\ORM\Internal\Hydration; |
|
21 |
|
22 use PDO, |
|
23 Doctrine\DBAL\Connection, |
|
24 Doctrine\DBAL\Types\Type, |
|
25 Doctrine\ORM\EntityManager; |
|
26 |
|
27 /** |
|
28 * Base class for all hydrators. A hydrator is a class that provides some form |
|
29 * of transformation of an SQL result set into another structure. |
|
30 * |
|
31 * @since 2.0 |
|
32 * @author Konsta Vesterinen <kvesteri@cc.hut.fi> |
|
33 * @author Roman Borschel <roman@code-factory.org> |
|
34 */ |
|
35 abstract class AbstractHydrator |
|
36 { |
|
37 /** @var ResultSetMapping The ResultSetMapping. */ |
|
38 protected $_rsm; |
|
39 |
|
40 /** @var EntityManager The EntityManager instance. */ |
|
41 protected $_em; |
|
42 |
|
43 /** @var AbstractPlatform The dbms Platform instance */ |
|
44 protected $_platform; |
|
45 |
|
46 /** @var UnitOfWork The UnitOfWork of the associated EntityManager. */ |
|
47 protected $_uow; |
|
48 |
|
49 /** @var array The cache used during row-by-row hydration. */ |
|
50 protected $_cache = array(); |
|
51 |
|
52 /** @var Statement The statement that provides the data to hydrate. */ |
|
53 protected $_stmt; |
|
54 |
|
55 /** @var array The query hints. */ |
|
56 protected $_hints; |
|
57 |
|
58 /** |
|
59 * Initializes a new instance of a class derived from <tt>AbstractHydrator</tt>. |
|
60 * |
|
61 * @param Doctrine\ORM\EntityManager $em The EntityManager to use. |
|
62 */ |
|
63 public function __construct(EntityManager $em) |
|
64 { |
|
65 $this->_em = $em; |
|
66 $this->_platform = $em->getConnection()->getDatabasePlatform(); |
|
67 $this->_uow = $em->getUnitOfWork(); |
|
68 } |
|
69 |
|
70 /** |
|
71 * Initiates a row-by-row hydration. |
|
72 * |
|
73 * @param object $stmt |
|
74 * @param object $resultSetMapping |
|
75 * @return IterableResult |
|
76 */ |
|
77 public function iterate($stmt, $resultSetMapping, array $hints = array()) |
|
78 { |
|
79 $this->_stmt = $stmt; |
|
80 $this->_rsm = $resultSetMapping; |
|
81 $this->_hints = $hints; |
|
82 $this->_prepare(); |
|
83 return new IterableResult($this); |
|
84 } |
|
85 |
|
86 /** |
|
87 * Hydrates all rows returned by the passed statement instance at once. |
|
88 * |
|
89 * @param object $stmt |
|
90 * @param object $resultSetMapping |
|
91 * @return mixed |
|
92 */ |
|
93 public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) |
|
94 { |
|
95 $this->_stmt = $stmt; |
|
96 $this->_rsm = $resultSetMapping; |
|
97 $this->_hints = $hints; |
|
98 $this->_prepare(); |
|
99 $result = $this->_hydrateAll(); |
|
100 $this->_cleanup(); |
|
101 return $result; |
|
102 } |
|
103 |
|
104 /** |
|
105 * Hydrates a single row returned by the current statement instance during |
|
106 * row-by-row hydration with {@link iterate()}. |
|
107 * |
|
108 * @return mixed |
|
109 */ |
|
110 public function hydrateRow() |
|
111 { |
|
112 $row = $this->_stmt->fetch(PDO::FETCH_ASSOC); |
|
113 if ( ! $row) { |
|
114 $this->_cleanup(); |
|
115 return false; |
|
116 } |
|
117 $result = array(); |
|
118 $this->_hydrateRow($row, $this->_cache, $result); |
|
119 return $result; |
|
120 } |
|
121 |
|
122 /** |
|
123 * Excutes one-time preparation tasks, once each time hydration is started |
|
124 * through {@link hydrateAll} or {@link iterate()}. |
|
125 */ |
|
126 protected function _prepare() |
|
127 {} |
|
128 |
|
129 /** |
|
130 * Excutes one-time cleanup tasks at the end of a hydration that was initiated |
|
131 * through {@link hydrateAll} or {@link iterate()}. |
|
132 */ |
|
133 protected function _cleanup() |
|
134 { |
|
135 $this->_rsm = null; |
|
136 $this->_stmt->closeCursor(); |
|
137 $this->_stmt = null; |
|
138 } |
|
139 |
|
140 /** |
|
141 * Hydrates a single row from the current statement instance. |
|
142 * |
|
143 * Template method. |
|
144 * |
|
145 * @param array $data The row data. |
|
146 * @param array $cache The cache to use. |
|
147 * @param mixed $result The result to fill. |
|
148 */ |
|
149 protected function _hydrateRow(array $data, array &$cache, array &$result) |
|
150 { |
|
151 throw new HydrationException("_hydrateRow() not implemented by this hydrator."); |
|
152 } |
|
153 |
|
154 /** |
|
155 * Hydrates all rows from the current statement instance at once. |
|
156 */ |
|
157 abstract protected function _hydrateAll(); |
|
158 |
|
159 /** |
|
160 * Processes a row of the result set. |
|
161 * Used for identity-based hydration (HYDRATE_OBJECT and HYDRATE_ARRAY). |
|
162 * Puts the elements of a result row into a new array, grouped by the class |
|
163 * they belong to. The column names in the result set are mapped to their |
|
164 * field names during this procedure as well as any necessary conversions on |
|
165 * the values applied. |
|
166 * |
|
167 * @return array An array with all the fields (name => value) of the data row, |
|
168 * grouped by their component alias. |
|
169 */ |
|
170 protected function _gatherRowData(array $data, array &$cache, array &$id, array &$nonemptyComponents) |
|
171 { |
|
172 $rowData = array(); |
|
173 |
|
174 foreach ($data as $key => $value) { |
|
175 // Parse each column name only once. Cache the results. |
|
176 if ( ! isset($cache[$key])) { |
|
177 if (isset($this->_rsm->scalarMappings[$key])) { |
|
178 $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; |
|
179 $cache[$key]['isScalar'] = true; |
|
180 } else if (isset($this->_rsm->fieldMappings[$key])) { |
|
181 $fieldName = $this->_rsm->fieldMappings[$key]; |
|
182 $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); |
|
183 $cache[$key]['fieldName'] = $fieldName; |
|
184 $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); |
|
185 $cache[$key]['isIdentifier'] = $classMetadata->isIdentifier($fieldName); |
|
186 $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; |
|
187 } else if (!isset($this->_rsm->metaMappings[$key])) { |
|
188 // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 |
|
189 // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. |
|
190 continue; |
|
191 } else { |
|
192 // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). |
|
193 $fieldName = $this->_rsm->metaMappings[$key]; |
|
194 $cache[$key]['isMetaColumn'] = true; |
|
195 $cache[$key]['fieldName'] = $fieldName; |
|
196 $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; |
|
197 $classMetadata = $this->_em->getClassMetadata($this->_rsm->aliasMap[$cache[$key]['dqlAlias']]); |
|
198 $cache[$key]['isIdentifier'] = isset($this->_rsm->isIdentifierColumn[$cache[$key]['dqlAlias']][$key]); |
|
199 } |
|
200 } |
|
201 |
|
202 if (isset($cache[$key]['isScalar'])) { |
|
203 $rowData['scalars'][$cache[$key]['fieldName']] = $value; |
|
204 continue; |
|
205 } |
|
206 |
|
207 $dqlAlias = $cache[$key]['dqlAlias']; |
|
208 |
|
209 if ($cache[$key]['isIdentifier']) { |
|
210 $id[$dqlAlias] .= '|' . $value; |
|
211 } |
|
212 |
|
213 if (isset($cache[$key]['isMetaColumn'])) { |
|
214 if (!isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) || $value !== null) { |
|
215 $rowData[$dqlAlias][$cache[$key]['fieldName']] = $value; |
|
216 } |
|
217 continue; |
|
218 } |
|
219 |
|
220 // in an inheritance hierachy the same field could be defined several times. |
|
221 // We overwrite this value so long we dont have a non-null value, that value we keep. |
|
222 // Per definition it cannot be that a field is defined several times and has several values. |
|
223 if (isset($rowData[$dqlAlias][$cache[$key]['fieldName']]) && $value === null) { |
|
224 continue; |
|
225 } |
|
226 |
|
227 $rowData[$dqlAlias][$cache[$key]['fieldName']] = $cache[$key]['type']->convertToPHPValue($value, $this->_platform); |
|
228 |
|
229 if ( ! isset($nonemptyComponents[$dqlAlias]) && $value !== null) { |
|
230 $nonemptyComponents[$dqlAlias] = true; |
|
231 } |
|
232 } |
|
233 |
|
234 return $rowData; |
|
235 } |
|
236 |
|
237 /** |
|
238 * Processes a row of the result set. |
|
239 * Used for HYDRATE_SCALAR. This is a variant of _gatherRowData() that |
|
240 * simply converts column names to field names and properly converts the |
|
241 * values according to their types. The resulting row has the same number |
|
242 * of elements as before. |
|
243 * |
|
244 * @param array $data |
|
245 * @param array $cache |
|
246 * @return array The processed row. |
|
247 */ |
|
248 protected function _gatherScalarRowData(&$data, &$cache) |
|
249 { |
|
250 $rowData = array(); |
|
251 |
|
252 foreach ($data as $key => $value) { |
|
253 // Parse each column name only once. Cache the results. |
|
254 if ( ! isset($cache[$key])) { |
|
255 if (isset($this->_rsm->scalarMappings[$key])) { |
|
256 $cache[$key]['fieldName'] = $this->_rsm->scalarMappings[$key]; |
|
257 $cache[$key]['isScalar'] = true; |
|
258 } else if (isset($this->_rsm->fieldMappings[$key])) { |
|
259 $fieldName = $this->_rsm->fieldMappings[$key]; |
|
260 $classMetadata = $this->_em->getClassMetadata($this->_rsm->declaringClasses[$key]); |
|
261 $cache[$key]['fieldName'] = $fieldName; |
|
262 $cache[$key]['type'] = Type::getType($classMetadata->fieldMappings[$fieldName]['type']); |
|
263 $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; |
|
264 } else if (!isset($this->_rsm->metaMappings[$key])) { |
|
265 // this column is a left over, maybe from a LIMIT query hack for example in Oracle or DB2 |
|
266 // maybe from an additional column that has not been defined in a NativeQuery ResultSetMapping. |
|
267 continue; |
|
268 } else { |
|
269 // Meta column (has meaning in relational schema only, i.e. foreign keys or discriminator columns). |
|
270 $cache[$key]['isMetaColumn'] = true; |
|
271 $cache[$key]['fieldName'] = $this->_rsm->metaMappings[$key]; |
|
272 $cache[$key]['dqlAlias'] = $this->_rsm->columnOwnerMap[$key]; |
|
273 } |
|
274 } |
|
275 |
|
276 $fieldName = $cache[$key]['fieldName']; |
|
277 |
|
278 if (isset($cache[$key]['isScalar'])) { |
|
279 $rowData[$fieldName] = $value; |
|
280 } else if (isset($cache[$key]['isMetaColumn'])) { |
|
281 $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $value; |
|
282 } else { |
|
283 $rowData[$cache[$key]['dqlAlias'] . '_' . $fieldName] = $cache[$key]['type'] |
|
284 ->convertToPHPValue($value, $this->_platform); |
|
285 } |
|
286 } |
|
287 |
|
288 return $rowData; |
|
289 } |
|
290 |
|
291 protected function registerManaged($class, $entity, $data) |
|
292 { |
|
293 if ($class->isIdentifierComposite) { |
|
294 $id = array(); |
|
295 foreach ($class->identifier as $fieldName) { |
|
296 if (isset($class->associationMappings[$fieldName])) { |
|
297 $id[$fieldName] = $data[$class->associationMappings[$fieldName]['joinColumns'][0]['name']]; |
|
298 } else { |
|
299 $id[$fieldName] = $data[$fieldName]; |
|
300 } |
|
301 } |
|
302 } else { |
|
303 if (isset($class->associationMappings[$class->identifier[0]])) { |
|
304 $id = array($class->identifier[0] => $data[$class->associationMappings[$class->identifier[0]]['joinColumns'][0]['name']]); |
|
305 } else { |
|
306 $id = array($class->identifier[0] => $data[$class->identifier[0]]); |
|
307 } |
|
308 } |
|
309 $this->_em->getUnitOfWork()->registerManaged($entity, $id, $data); |
|
310 } |
|
311 } |