* renamed TcaMapper to ObjectRelationalMapper
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Mapper / TX_EXTMVC_Persistence_Mapper_ObjectRelationalMapper.php
1 <?php
2 declare(ENCODING = 'utf-8');
3
4 /* *
5 * This script belongs to the FLOW3 framework. *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License as published by the *
9 * Free Software Foundation, either version 3 of the License, or (at your *
10 * option) any later version. *
11 * *
12 * This script is distributed in the hope that it will be useful, but *
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
14 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
15 * General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU Lesser General Public *
18 * License along with the script. *
19 * If not, see http://www.gnu.org/licenses/lgpl.html *
20 * *
21 * The TYPO3 project - inspiring people to share! *
22 * */
23
24 require_once(PATH_t3lib . 'interfaces/interface.t3lib_singleton.php');
25 require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/Utility/TX_EXTMVC_Utility_Strings.php');
26 require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/Persistence/TX_EXTMVC_Persistence_ObjectStorage.php');
27 require_once(t3lib_extMgm::extPath('extmvc') . 'Classes/Persistence/Mapper/TX_EXTMVC_Persistence_Mapper_DataMap.php');
28
29 /**
30 * A mapper to map database tables configured in $TCA on domain objects.
31 *
32 * @version $Id:$
33 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
34 */
35 class TX_EXTMVC_Persistence_Mapper_ObjectRelationalMapper implements t3lib_Singleton {
36
37 /**
38 * The content object
39 *
40 * @var tslib_cObj
41 **/
42 protected $cObj;
43
44 /**
45 * The persistence session
46 *
47 * @var TX_EXTMVC_Persistence_Session
48 **/
49 protected $session;
50
51 /**
52 * Constructs a new mapper
53 *
54 * @author Jochen Rau <jochen.rau@typoplanet.de>
55 */
56 public function __construct() {
57 $this->cObj = t3lib_div::makeInstance('tslib_cObj');
58 $this->session = t3lib_div::makeInstance('TX_EXTMVC_Persistence_Session');
59 $GLOBALS['TSFE']->includeTCA();
60 }
61
62 /**
63 * Finds objects matching property="xyz"
64 *
65 * @param string $propertyName The name of the property (will be chekced by a white list)
66 * @param string $arguments The WHERE statement
67 * @return void
68 * @author Jochen Rau <jochen.rau@typoplanet.de>
69 */
70 public function findWhere($className, $where = '1=1') {
71 $dataMap = $this->getDataMap($className);
72 $rows = $this->fetch($dataMap, $where);
73 $objects = $this->reconstituteObjects($dataMap, $rows);
74 return $objects;
75 }
76
77 /**
78 * Fetches rows from the database by given SQL statement snippets
79 *
80 * @param string $from FROM statement
81 * @param string $where WHERE statement
82 * @param string $groupBy GROUP BY statement
83 * @param string $orderBy ORDER BY statement
84 * @param string $limit LIMIT statement
85 * @return void
86 * @author Jochen Rau <jochen.rau@typoplanet.de>
87 */
88 public function fetch($dataMap, $where = '1=1', $groupBy = NULL, $orderBy = NULL, $limit = NULL) {
89 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
90 '*', // TODO limit fetched fields
91 $dataMap->getTableName(),
92 $where . $this->cObj->enableFields($dataMap->getTableName()) . $this->cObj->enableFields($dataMap->getTableName()),
93 $groupBy,
94 $orderBy,
95 $limit
96 );
97 // TODO language overlay; workspace overlay
98 return $rows ? $rows : array();
99 }
100
101 /**
102 * Fetches a rows from the database by given SQL statement snippets taking a relation table into account
103 *
104 * @author Jochen Rau <jochen.rau@typoplanet.de>
105 */
106 public function fetchWithRelationTable($parentObject, $columnMap, $where = '1=1', $groupBy = NULL, $orderBy = NULL, $limit = NULL) {
107 $rows = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
108 $columnMap->getChildTableName() . '.*, ' . $columnMap->getRelationTableName() . '.*',
109 $columnMap->getChildTableName() . ' LEFT JOIN ' . $columnMap->getRelationTableName() . ' ON (' . $columnMap->getChildTableName() . '.uid=' . $columnMap->getRelationTableName() . '.uid_foreign)',
110 $where . ' AND ' . $columnMap->getRelationTableName() . '.uid_local=' . intval($parentObject->getUid()) . $this->cObj->enableFields($columnMap->getChildTableName()) . $this->cObj->enableFields($columnMap->getChildTableName()),
111 $groupBy,
112 $orderBy,
113 $limit
114 );
115 // TODO language overlay; workspace overlay
116 return $rows ? $rows : array();
117 }
118
119 /**
120 * reconstitutes domain objects from $rows (array)
121 *
122 * @param TX_EXTMVC_Persistence_Mapper_DataMap $dataMap The data map corresponding to the domain object
123 * @param array $rows The rows array fetched from the database
124 * @return array An array of reconstituted domain objects
125 * @author Jochen Rau <jochen.rau@typoplanet.de>
126 */
127 protected function reconstituteObjects($dataMap, array $rows) {
128 $objects = array();
129 foreach ($rows as $row) {
130 $properties = array();
131 foreach ($dataMap->getColumnMaps() as $columnMap) {
132 $properties[$columnMap->getPropertyName()] = $dataMap->convertFieldValueToPropertyValue($columnMap->getPropertyName(), $row[$columnMap->getColumnName()]);
133 }
134 $object = $this->reconstituteObject($dataMap->getClassName(), $properties);
135 foreach ($dataMap->getColumnMaps() as $columnMap) {
136 if ($columnMap->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
137 $where .= $columnMap->getParentKeyFieldName() . '=' . intval($object->getUid());
138 $relatedDataMap = $this->getDataMap($columnMap->getChildClassName());
139 $relatedRows = $this->fetch($relatedDataMap, $where);
140 $relatedObjects = $this->reconstituteObjects($relatedDataMap, $relatedRows, $depth);
141 $object->_reconstituteProperty($columnMap->getPropertyName(), $relatedObjects);
142 } elseif ($columnMap->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
143 $relatedDataMap = $this->getDataMap($columnMap->getChildClassName());
144 $relatedRows = $this->fetchWithRelationTable($object, $columnMap);
145 $relatedObjects = $this->reconstituteObjects($relatedDataMap, $relatedRows, $depth);
146 $object->_reconstituteProperty($columnMap->getPropertyName(), $relatedObjects);
147 }
148 }
149 $this->session->registerReconstitutedObject($object);
150 $objects[] = $object;
151 }
152 return $objects;
153 }
154
155 /**
156 * Reconstitutes the specified object and fills it with the given properties.
157 *
158 * @param string $objectName Name of the object to reconstitute
159 * @param array $properties The names of properties and their values which should be set during the reconstitution
160 * @return object The reconstituted object
161 * @author Robert Lemke <robert@typo3.org>
162 */
163 protected function reconstituteObject($className, array $properties = array()) {
164 // those objects will be fetched from within the __wakeup() method of the object...
165 $GLOBALS['EXTMVC']['reconstituteObject']['properties'] = $properties;
166 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
167 unset($GLOBALS['EXTMVC']['reconstituteObject']);
168 return $object;
169 }
170
171 /**
172 * Persists all objects of a persistence session
173 *
174 * @return void
175 * @author Jochen Rau <jochen.rau@typoplanet.de>
176 */
177 public function persistAll() {
178 // first, persit all aggregate root objects
179 $aggregateRootClassNames = $this->session->getAggregateRootClassNames();
180 foreach ($aggregateRootClassNames as $className) {
181 $this->persistObjects($className);
182 }
183 // persist all remaining objects
184 $this->persistObjects();
185 }
186
187 /**
188 * Persists all objects of a persitance session that are of a given class. If there
189 * is no class specified, it persits all objects of a session.
190 *
191 * @param string $className Name of the class of the objects to be persisted
192 * @author Jochen Rau <jochen.rau@typoplanet.de>
193 */
194 protected function persistObjects($className = NULL) {
195 foreach ($this->session->getAddedObjects($className) as $object) {
196 $this->insertObject($object);
197 $this->session->unregisterAddedObject($object);
198 }
199 foreach ($this->session->getDirtyObjects($className) as $object) {
200 $this->updateObject($object);
201 $this->session->unregisterObject($object); // TODO is this necessary?
202 $this->session->registerReconstitutedObject($object);
203 }
204 foreach ($this->session->getRemovedObjects($className) as $object) {
205 $this->deleteObject($object);
206 $this->session->unregisterRemovedObject($object);
207 }
208 }
209
210 /**
211 * Inserts an object to the database.
212 *
213 * @return void
214 * @author Jochen Rau <jochen.rau@typoplanet.de>
215 */
216 public function insertObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object, $parentObject = NULL, $parentPropertyName = NULL) {
217 $queuedRelations = array();
218 $row = array();
219 $properties = $object->_getProperties();
220 $dataMap = $this->getDataMap(get_class($object));
221 foreach ($dataMap->getColumnMaps() as $columnMap) {
222 $propertyName = $columnMap->getPropertyName();
223 $columnName = $columnMap->getColumnName();
224 if ($columnMap->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
225 $queuedRelations[$propertyName] = $properties[$propertyName];
226 $row[$columnName] = count($properties[$propertyName]);
227 } elseif ($columnMap->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
228 $queuedRelations[$propertyName] = $properties[$propertyName];
229 $row[$columnName] = count($properties[$propertyName]);
230 } else {
231 if ($properties[$propertyName] !== NULL) {
232 $row[$columnName] = $dataMap->convertPropertyValueToFieldValue($properties[$propertyName]);
233 }
234 }
235 }
236
237 if ($parentObject instanceof TX_EXTMVC_DomainObject_AbstractDomainObject && $parentPropertyName !== NULL) {
238 $parentDataMap = $this->getDataMap(get_class($parentObject));
239 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
240 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
241 if ($parentKeyFieldName !== NULL) {
242 $row[$parentKeyFieldName] = $parentObject->getUid();
243 }
244 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
245 if ($parentTableFieldName !== NULL) {
246 $row[$parentTableFieldName] = $parentDataMap->getTableName();
247 }
248 }
249
250 unset($row['uid']);
251 $row['pid'] = !empty($this->cObj->data['pages']) ? $this->cObj->data['pages'] : $GLOBALS['TSFE']->id;
252 $row['tstamp'] = time();
253
254 $tableName = $dataMap->getTableName();
255 $res = $GLOBALS['TYPO3_DB']->exec_INSERTquery(
256 $tableName,
257 $row
258 );
259 $object->_reconstituteProperty('uid', $GLOBALS['TYPO3_DB']->sql_insert_id());
260
261 foreach ($queuedRelations as $propertyName => $relatedObjects) {
262 foreach ($relatedObjects as $relatedObject) {
263 if (!$this->session->isReconstitutedObject($relatedObject)) {
264 $this->insertObject($relatedObject, $object, $propertyName);
265 if ($dataMap->getColumnMap($propertyName)->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
266 $this->insertRelation($relatedObject, $object, $propertyName);
267 } elseif ($this->session->isReconstitutedObject($relatedObject) && $relatedObject->_isDirty()) {
268 $this->updateObject($relatedObject, $object, $propertyName);
269 }
270 }
271 }
272 }
273 }
274
275 /**
276 * Inserts relation to a relation table
277 *
278 * @param TX_EXTMVC_DomainObject_AbstractDomainObject $parentObject The parent object
279 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
280 * @param TX_EXTMVC_DomainObject_AbstractDomainObject $relatedObject The related object
281 * @return void
282 * @author Jochen Rau <jochen.rau@typoplanet.de>
283 */
284 protected function insertRelation( TX_EXTMVC_DomainObject_AbstractDomainObject $relatedObject, TX_EXTMVC_DomainObject_AbstractDomainObject $parentObject, $parentPropertyName) {
285 $dataMap = $this->getDataMap(get_class($parentObject));
286 $rowToInsert = array(
287 'uid_local' => $parentObject->getUid(),
288 'uid_foreign' => $relatedObject->getUid(),
289 'tablenames' => $dataMap->getTableName(),
290 'sorting' => 9999 // TODO sorting of mm table items
291 );
292 $tableName = $dataMap->getColumnMap($parentPropertyName)->getRelationTableName();
293 $res = $GLOBALS['TYPO3_DB']->exec_INSERTquery(
294 $tableName,
295 $rowToInsert
296 );
297 }
298
299 /**
300 * Updates a modified object in the database
301 *
302 * @return void
303 * @author Karsten Dambekalns <karsten@typo3.org>
304 * @author Jochen Rau <jochen.rau@typoplanet.de>
305 */
306 public function updateObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object, $parentObject = NULL, $parentPropertyName = NULL) {
307 $queuedRelations = array();
308 $row = array();
309 $properties = $object->_getDirtyProperties();
310 $dataMap = $this->getDataMap(get_class($object));
311 foreach ($dataMap->getColumnMaps() as $columnMap) {
312 $propertyName = $columnMap->getPropertyName();
313 $columnName = $columnMap->getColumnName();
314 if ($columnMap->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
315 $queuedRelations[$propertyName] = $properties[$propertyName];
316 $row[$columnName] = count($properties[$propertyName]);
317 } elseif ($columnMap->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
318 $queuedRelations[$propertyName] = $properties[$propertyName];
319 $row[$columnName] = count($properties[$propertyName]);
320 } else {
321 if ($properties[$propertyName] !== NULL) {
322 $row[$columnName] = $dataMap->convertPropertyValueToFieldValue($properties[$propertyName]);
323 }
324 }
325 }
326
327 unset($row['uid']);
328 $row['crdate'] = time();
329 if (!empty($GLOBALS['TSFE']->fe_user->user['uid'])) {
330 $row['cruser_id'] = $GLOBALS['TSFE']->fe_user->user['uid'];
331 }
332 if ($parentObject instanceof TX_EXTMVC_DomainObject_AbstractDomainObject && $parentPropertyName !== NULL) {
333 $parentDataMap = $this->getDataMap(get_class($parentObject));
334 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
335 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
336 if ($parentKeyFieldName !== NULL) {
337 $row[$parentKeyFieldName] = $parentObject->getUid();
338 }
339 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
340 if ($parentTableFieldName !== NULL) {
341 $row[$parentTableFieldName] = $parentDataMap->getTableName();
342 }
343 }
344 $tableName = $dataMap->getTableName();
345 $res = $GLOBALS['TYPO3_DB']->exec_UPDATEquery(
346 $tableName,
347 'uid=' . $object->getUid(),
348 $row
349 );
350
351 foreach ($queuedRelations as $propertyName => $relatedObjects) {
352 foreach ($relatedObjects as $relatedObject) {
353 if (!$this->session->isReconstitutedObject($relatedObject)) {
354 $this->insertObject($relatedObject, $object, $propertyName);
355 if ($dataMap->getColumnMap($propertyName)->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
356 $this->insertRelation($relatedObject, $object, $propertyName);
357 }
358 } elseif ($this->session->isReconstitutedObject($relatedObject) && $relatedObject->_isDirty()) {
359 $this->updateObject($relatedObject, $object, $propertyName);
360 }
361 }
362 }
363 }
364
365 /**
366 * Deletes an object, it's 1:n related objects, and the m:n relations in relation tables (but not the m:n related objects!)
367 *
368 * @return void
369 * @author Jochen Rau <jochen.rau@typoplanet.de>
370 */
371 public function deleteObject(TX_EXTMVC_DomainObject_AbstractDomainObject $object, $parentObject = NULL, $parentPropertyName = NULL, $recursionMode = FALSE) {
372 $queuedRelations = array();
373 $properties = $object->_getDirtyProperties();
374 $dataMap = $this->getDataMap(get_class($object));
375 foreach ($dataMap->getColumnMaps() as $columnMap) {
376 $propertyName = $columnMap->getPropertyName();
377 $columnName = $columnMap->getColumnName();
378 if ($columnMap->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
379 $queuedRelations[$propertyName] = $properties[$propertyName];
380 } elseif ($columnMap->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
381 $queuedRelations[$propertyName] = $properties[$propertyName];
382 }
383 }
384
385 $tableName = $dataMap->getTableName();
386 $res = $GLOBALS['TYPO3_DB']->exec_DELETEquery(
387 $tableName,
388 'uid=' . $object->getUid()
389 );
390
391 if ($recursionMode === TRUE) {
392 foreach ($queuedRelations as $propertyName => $relatedObjects) {
393 foreach ($relatedObjects as $relatedObject) {
394 $this->deleteObject($relatedObject, $object, $propertyName);
395 if ($dataMap->getColumnMap($propertyName)->getTypeOfRelation() === TX_EXTMVC_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
396 $this->deleteRelation($relatedObject, $object, $propertyName);
397 }
398 }
399 }
400 }
401
402 }
403
404 /**
405 * Inserts relation to a relation table
406 *
407 * @param TX_EXTMVC_DomainObject_AbstractDomainObject $parentObject The parent object
408 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
409 * @param TX_EXTMVC_DomainObject_AbstractDomainObject $relatedObject The related object
410 * @return void
411 * @author Jochen Rau <jochen.rau@typoplanet.de>
412 */
413 protected function deleteRelations(TX_EXTMVC_DomainObject_AbstractDomainObject $parentObject, $parentPropertyName, TX_EXTMVC_DomainObject_AbstractDomainObject $relatedObject) {
414 $tableName = $this->getRelationTableName(get_class($parentObject), $parentPropertyName);
415 $res = $GLOBALS['TYPO3_DB']->exec_DELETEquery(
416 $tableName,
417 'uid_local=' . $parentObject->getUid()
418 );
419 }
420
421 /**
422 * Delegates the call to the Data Map.
423 * Returns TRUE if the property is persistable (configured in $TCA)
424 *
425 * @param string $className The property name
426 * @param string $propertyName The property name
427 * @return boolean TRUE if the property is persistable (configured in $TCA)
428 * @author Jochen Rau <jochen.rau@typoplanet.de>
429 */
430 public function isPersistableProperty($className, $propertyName) {
431 $dataMapClassName = t3lib_div::makeInstanceClassName('TX_EXTMVC_Persistence_Mapper_DataMap');
432 $dataMap = new $dataMapClassName($className);
433 $dataMap->initialize();
434 return $dataMap->isPersistableProperty($propertyName);
435 }
436
437 /**
438 * Returns a data map for a given class name
439 *
440 * @return TX_EXTMVC_Persistence_Mapper_DataMap The data map
441 * @author Jochen Rau <jochen.rau@typoplanet.de>
442 */
443 protected function getDataMap($className) {
444 $dataMapClassName = t3lib_div::makeInstanceClassName('TX_EXTMVC_Persistence_Mapper_DataMap');
445 // TODO Cache data maps
446 $dataMap = new $dataMapClassName($className);
447 $dataMap->initialize();
448 return $dataMap;
449 }
450
451 }
452 ?>