* The object relational mapper will gather statistics about database operations and...
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Mapper / ObjectRelationalMapper.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 *
17 * This script is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * This copyright notice MUST APPEAR in all copies of the script!
23 ***************************************************************/
24
25 require_once(PATH_t3lib . 'interfaces/interface.t3lib_singleton.php');
26 require_once(PATH_tslib . 'class.tslib_content.php');
27
28 /**
29 * A mapper to map database tables configured in $TCA on domain objects.
30 *
31 * @package TYPO3
32 * @subpackage extbase
33 * @version $ID:$
34 */
35 class Tx_ExtBase_Persistence_Mapper_ObjectRelationalMapper implements t3lib_Singleton {
36
37 /**
38 * The persistence session
39 *
40 * @var Tx_ExtBase_Persistence_Session
41 **/
42 protected $persistenceSession;
43
44 /**
45 * Cached data maps
46 *
47 * @var array
48 **/
49 protected $dataMaps = array();
50
51 /**
52 * The TYPO3 DB object
53 *
54 * @var t3lib_db
55 **/
56 protected $database;
57
58 /**
59 * Statistics with counts of database operations
60 *
61 * @var array
62 **/
63 protected $statistics = array();
64
65 /**
66 * A first level cache for domain objects by class and uid
67 *
68 * @var array
69 **/
70 protected $identityMap = array();
71
72 /**
73 * Constructs a new mapper
74 *
75 */
76 public function __construct() {
77 $this->persistenceSession = t3lib_div::makeInstance('Tx_ExtBase_Persistence_Session');
78 $GLOBALS['TSFE']->includeTCA();
79 $this->database = $GLOBALS['TYPO3_DB'];
80 }
81
82 /**
83 * The build query method is invoked by the Persistence Repository.
84 * Build a query for objects by multiple conditions. Either as SQL parts or query by example.
85 *
86 * The following condition array would find entities with description like the given keyword and
87 * name equal to "foo".
88 *
89 * <pre>
90 * array(
91 * array('blog_description LIKE ?', $keyword),
92 * 'blogName' => 'Foo'
93 * )
94 * </pre>
95 *
96 * Note: The SQL part uses the database columns names, the query by example syntax uses
97 * the object property name (camel-cased, without underscore).
98 *
99 * @param array|string $conditions The conditions as an array or SQL string
100 * @return string The query where part for the class and given conditions
101 */
102 public function buildQuery($className, $conditions) {
103 $dataMap = $this->getDataMap($className);
104 if (is_array($conditions)) {
105 $where = $this->buildQueryByConditions($dataMap, $conditions);
106 } if (is_string($conditions)) {
107 $where = $conditions;
108 }
109 return $where;
110 }
111
112 /**
113 * Get a where part for conditions by a specific data map. This will
114 * either replace placeholders (index based array) or use the condition
115 * as an example relative to the data map.
116 *
117 * @param Tx_ExtBase_Persistence_Mapper_DataMap $dataMap The data map
118 * @param array $conditions The conditions
119 *
120 * @return string The where part
121 */
122 protected function buildQueryByConditions(&$dataMap, $conditions) {
123 $whereParts = array();
124 foreach ($conditions as $key => $condition) {
125 if (is_array($condition) && isset($condition[0])) {
126 $sql = $this->replacePlaceholders($dataMap, $condition[0], array_slice($condition, 1));
127 $whereParts[] = '(' . $sql . ')';
128 } elseif (is_string($key)) {
129 $sql = $this->buildQueryByExample($dataMap, $key, $condition);
130 if (strlen($sql) > 0) {
131 $whereParts[] = '(' . $sql . ')';
132 }
133 }
134 }
135 return implode(' AND ', $whereParts);
136 }
137
138 /**
139 * Get a where part for an example condition (associative array). This also works
140 * for nested conditions.
141 *
142 * @param Tx_ExtBase_Persistence_Mapper_DataMap $dataMap The data map
143 * @param array $propertyName The property name
144 * @param array $example The example condition
145 *
146 * @return string The where part
147 */
148 protected function buildQueryByExample(&$dataMap, $propertyName, $example) {
149 $sql = '';
150 $columnMap = $dataMap->getColumnMap($propertyName);
151 if (!$columnMap) {
152 echo "No columnMap for $propertyName";
153 }
154 if (!is_array($example)) {
155 $column = $dataMap->getTableName() . '.' . $columnMap->getColumnName();
156 $sql = $column . ' = ' . $dataMap->convertPropertyValueToFieldValue($example);
157 } else {
158 $childDataMap = $this->getDataMap($columnMap->getChildClassName());
159 $sql = $this->buildQueryByConditions($childDataMap, $example);
160 }
161 return $sql;
162 }
163
164 /**
165 * Replace query placeholders in a query part by the given
166 * parameters.
167 *
168 * @param Tx_ExtBase_Persistence_Mapper_DataMap $dataMap The data map for conversion
169 * @param string $queryPart The query part with placeholders
170 * @param array $parameters The parameters
171 *
172 * @return string The query part with replaced placeholders
173 */
174 protected function replacePlaceholders(&$dataMap, $queryPart, $parameters) {
175 $sql = $queryPart;
176 foreach ($parameters as $parameter) {
177 $markPosition = strpos($sql, '?');
178 if ($markPosition !== FALSE) {
179 $sql = substr($sql, 0, $markPosition) . $dataMap->convertPropertyValueToFieldValue($parameter) . substr($sql, $markPosition + 1);
180 }
181 }
182 return $sql;
183 }
184
185 /**
186 * Fetches objects from the database by given SQL statement snippets. The where
187 * statement is raw SQL and will not be escaped. It is much safer to use the
188 * generic find method to supply where conditions.
189 *
190 * @param string $className the className
191 * @param string $where WHERE statement
192 * @param string $from FROM statement will default to the tablename of the given class
193 * @param string $groupBy GROUP BY statement
194 * @param string $orderBy ORDER BY statement
195 * @param string $limit LIMIT statement
196 * @return array The matched rows
197 */
198 public function fetch($className, $where = '', $from = '', $groupBy = '', $orderBy = '', $limit = '', $useEnableFields = TRUE) {
199 if (!strlen($where)) {
200 $where = '1=1';
201 }
202 $dataMap = $this->getDataMap($className);
203 $joinClause = $this->getJoinClause($className);
204 if (!strlen($from)) {
205 $from = $dataMap->getTableName() . ' ' . $joinClause;
206 }
207 if ($useEnableFields === TRUE) {
208 $enableFields = $GLOBALS['TSFE']->sys_page->enableFields($dataMap->getTableName());
209 // TODO CH: add enable fields for joined tables
210 } else {
211 $enableFields = '';
212 }
213
214 $res = $this->database->exec_SELECTquery(
215 '*',
216 $from,
217 $where . $enableFields,
218 $groupBy,
219 $orderBy,
220 $limit
221 );
222 $this->statistics['select']++;
223
224 $fieldMap = $this->getFieldMapFromResult($res);
225 $rows = $this->getRowsFromResult($res);
226 $this->database->sql_free_result($res);
227
228 // SK: Do we want to make it possible to ignore "enableFields"?
229 // TODO language overlay; workspace overlay
230 $objects = array();
231 if (is_array($rows)) {
232 if (count($rows) > 0) {
233 $objects = $this->reconstituteObjects($dataMap, $fieldMap, $rows);
234 }
235 }
236 $this->statistics['fetch']++;
237 return $objects;
238 }
239
240 protected function getFieldMapFromResult($res) {
241 $fieldMap = array();
242 if ($res !== FALSE) {
243 $fieldPosition = 0;
244 // FIXME mysql_fetch_field should be available in t3lib_db (patch core)
245 while ($field = mysql_fetch_field($res)) {
246 $fieldMap[$field->table][$field->name] = $fieldPosition;
247 $fieldPosition++;
248 }
249 }
250 return $fieldMap;
251 }
252
253 protected function getRowsFromResult($res) {
254 $rows = array();
255 if ($res !== FALSE) {
256 while($rows[] = $this->database->sql_fetch_row($res));
257 array_pop($rows);
258 }
259 return $rows;
260 }
261
262 /**
263 * Get the join clause for the fetch method for a specific class. This will
264 * eagerly load all has-one relations.
265 *
266 * @param string $className The class name
267 * @return string The join clause
268 */
269 protected function getJoinClause($className) {
270 $dataMap = $this->getDataMap($className);
271 $join = '';
272 foreach ($dataMap->getColumnMaps() as $propertyName => $columnMap) {
273 if ($columnMap->getTypeOfRelation() == Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
274 $join .= ' LEFT JOIN ' . $columnMap->getChildTableName() . ' ON ' . $dataMap->getTableName() . '.' . $columnMap->getColumnName() . ' = ' . $columnMap->getChildTableName() . '.uid';
275 $join .= $this->getJoinClause($columnMap->getChildClassName());
276 }
277 }
278 return $join;
279 }
280
281 /**
282 * Fetches a rows from the database by given SQL statement snippets taking a relation table into account
283 *
284 * @param string Optional WHERE clauses put in the end of the query, defaults to '1=1. NOTICE: You must escape values in this argument with $this->fullQuoteStr() yourself!
285 * @param string Optional GROUP BY field(s), defaults to blank string.
286 * @param string Optional ORDER BY field(s), defaults to blank string.
287 * @param string Optional LIMIT value ([begin,]max), defaults to blank string.
288 */
289 public function fetchWithRelationTable($parentObject, $columnMap, $where = '', $groupBy = '', $orderBy = '', $limit = '', $useEnableFields = TRUE) {
290 if (!strlen($where)) {
291 $where = '1=1';
292 }
293 $from = $columnMap->getChildTableName() . ' LEFT JOIN ' . $columnMap->getRelationTableName() . ' ON (' . $columnMap->getChildTableName() . '.uid=' . $columnMap->getRelationTableName() . '.uid_foreign)';
294 $where .= ' AND ' . $columnMap->getRelationTableName() . '.uid_local=' . t3lib_div::intval_positive($parentObject->getUid());
295
296 return $this->fetch($columnMap->getChildClassName(), $where, $from, $groupBy, $orderBy, $limit, $useEnableFields);
297 }
298
299 /**
300 * reconstitutes domain objects from $rows (array)
301 *
302 * @param Tx_ExtBase_Persistence_Mapper_DataMap $dataMap The data map corresponding to the domain object
303 * @param array $fieldMap An array indexed by the table name and field name to the row index
304 * @param array $rows The rows array fetched from the database (not associative)
305 * @return array An array of reconstituted domain objects
306 */
307 // SK: I Need to check this method more thoroughly.
308 // SK: Are loops detected during reconstitution?
309 protected function reconstituteObjects(Tx_ExtBase_Persistence_Mapper_DataMap $dataMap, array &$fieldMap, array &$rows) {
310 $objects = array();
311 foreach ($rows as $row) {
312 $properties = $this->getProperties($dataMap, $fieldMap, $row);
313 $identity = $properties['uid'];
314 $className = $dataMap->getClassName();
315 if (isset($this->identityMap[$className][$identity])) {
316 $object = $this->identityMap[$className][$identity];
317 } else {
318 $object = $this->reconstituteObject($dataMap->getClassName(), $properties);
319 foreach ($dataMap->getColumnMaps() as $columnMap) {
320 if ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
321 list($relatedObject) = $this->reconstituteObjects($this->getDataMap($columnMap->getChildClassName()), $fieldMap, array($row));
322 $object->_reconstituteProperty($columnMap->getPropertyName(), $relatedObject);
323 } elseif ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
324 $where = $columnMap->getParentKeyFieldName() . '=' . intval($object->getUid());
325 $relatedDataMap = $this->getDataMap($columnMap->getChildClassName());
326 $relatedObjects = $this->fetch($columnMap->getChildClassName(), $where);
327 $object->_reconstituteProperty($columnMap->getPropertyName(), $relatedObjects);
328 } elseif ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
329 $relatedDataMap = $this->getDataMap($columnMap->getChildClassName());
330 $relatedObjects = $this->fetchWithRelationTable($object, $columnMap);
331 $object->_reconstituteProperty($columnMap->getPropertyName(), $relatedObjects);
332 }
333 }
334 $this->persistenceSession->registerReconstitutedObject($object);
335 $this->identityMap[$className][$identity] = $object;
336 }
337 $objects[] = $object;
338 }
339 return $objects;
340 }
341
342 protected function getProperties(Tx_ExtBase_Persistence_Mapper_DataMap $dataMap, array &$fieldMap, array &$row) {
343 $properties = array();
344 foreach ($dataMap->getColumnMaps() as $columnMap) {
345 $fieldValue = $row[$fieldMap[$dataMap->getTableName()][$columnMap->getColumnName()]];
346 $properties[$columnMap->getPropertyName()] = $dataMap->convertFieldValueToPropertyValue($columnMap->getPropertyName(), $fieldValue);
347 }
348 return $properties;
349 }
350
351 /**
352 * Reconstitutes the specified object and fills it with the given properties.
353 *
354 * @param string $objectName Name of the object to reconstitute
355 * @param array $properties The names of properties and their values which should be set during the reconstitution
356 * @return object The reconstituted object
357 */
358 protected function reconstituteObject($className, array $properties = array()) {
359 // those objects will be fetched from within the __wakeup() method of the object...
360 $GLOBALS['ExtBase']['reconstituteObject']['properties'] = $properties;
361 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
362 unset($GLOBALS['ExtBase']['reconstituteObject']);
363 $this->statistics['reconstitute']++;
364 return $object;
365 }
366
367 /**
368 * Persists all objects of a persistence session
369 *
370 * @return void
371 */
372 public function persistAll() {
373 // first, persist all aggregate root objects
374 $aggregateRootClassNames = $this->persistenceSession->getAggregateRootClassNames();
375 foreach ($aggregateRootClassNames as $className) {
376 $this->persistObjects($className);
377 }
378 // persist all remaining objects registered manually
379 $this->persistObjects();
380 }
381
382 /**
383 * Persists all objects of a persitance persistence session that are of a given class. If there
384 * is no class specified, it persits all objects of a persistence session.
385 *
386 * @param string $className Name of the class of the objects to be persisted
387 */
388 protected function persistObjects($className = NULL) {
389 foreach ($this->persistenceSession->getAddedObjects($className) as $object) {
390 $this->insertObject($object);
391 $this->persistenceSession->unregisterObject($object);
392 $this->persistenceSession->registerReconstitutedObject($object);
393 }
394 foreach ($this->persistenceSession->getRemovedObjects($className) as $object) {
395 $this->deleteObject($object);
396 $this->persistenceSession->unregisterObject($object);
397 }
398 foreach ($this->persistenceSession->getDirtyObjects($className) as $object) {
399 $this->updateObject($object);
400 $this->persistenceSession->unregisterObject($object);
401 $this->persistenceSession->registerReconstitutedObject($object);
402 }
403 }
404
405 /**
406 * Inserts an object to the database.
407 *
408 * @return void
409 */
410 // SK: I need to check this more thorougly
411 protected function insertObject(Tx_ExtBase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, $recurseIntoRelations = TRUE) {
412 $properties = $object->_getProperties();
413 $dataMap = $this->getDataMap(get_class($object));
414 $row = $this->getRow($dataMap, $properties);
415
416 if ($parentObject instanceof Tx_ExtBase_DomainObject_DomainObjectInterface && $parentPropertyName !== NULL) {
417 $parentDataMap = $this->getDataMap(get_class($parentObject));
418 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
419 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
420 if ($parentKeyFieldName !== NULL) {
421 $row[$parentKeyFieldName] = $parentObject->getUid();
422 }
423 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
424 if ($parentTableFieldName !== NULL) {
425 $row[$parentTableFieldName] = $parentDataMap->getTableName();
426 }
427 }
428
429 if ($dataMap->hasPidColumn()) {
430 $row['pid'] = !empty($this->cObj->data['pages']) ? $this->cObj->data['pages'] : $GLOBALS['TSFE']->id;
431 }
432 if ($dataMap->hasCreationDateColumn()) {
433 $row[$dataMap->getCreationDateColumnName()] = time();
434 }
435 if ($dataMap->hasTimestampColumn()) {
436 $row[$dataMap->getTimestampColumnName()] = time();
437 }
438 unset($row['uid']);
439 $tableName = $dataMap->getTableName();
440 $res = $this->database->exec_INSERTquery(
441 $tableName,
442 $row
443 );
444 $this->statistics['insert']++;
445 $object->_reconstituteProperty('uid', $this->database->sql_insert_id());
446
447 $this->persistRelations($object, $propertyName, $this->getRelations($dataMap, $properties));
448 }
449
450 /**
451 * Updates a modified object in the database
452 *
453 * @return void
454 */
455 // SK: I need to check this more thorougly
456 protected function updateObject(Tx_ExtBase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, $recurseIntoRelations = TRUE) {
457 $properties = $object->_getDirtyProperties();
458 $dataMap = $this->getDataMap(get_class($object));
459 $row = $this->getRow($dataMap, $properties);
460
461 if ($parentObject instanceof Tx_ExtBase_DomainObject_DomainObjectInterface && $parentPropertyName !== NULL) {
462 $parentDataMap = $this->getDataMap(get_class($parentObject));
463 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
464 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
465 if ($parentKeyFieldName !== NULL) {
466 $row[$parentKeyFieldName] = $parentObject->getUid();
467 }
468 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
469 if ($parentTableFieldName !== NULL) {
470 $row[$parentTableFieldName] = $parentDataMap->getTableName();
471 }
472 }
473
474 unset($row['uid']);
475 if ($dataMap->hasTimestampColumn()) {
476 $row[$dataMap->getTimestampColumnName()] = time();
477 }
478 if ($dataMap->hasCreatorUidColumn() && !empty($GLOBALS['TSFE']->fe_user->user['uid'])) {
479 $row[$dataMap->getCreatorUidColumnName()] = $GLOBALS['TSFE']->fe_user->user['uid'];
480 }
481 $tableName = $dataMap->getTableName();
482 $res = $this->database->exec_UPDATEquery(
483 $tableName,
484 'uid=' . $object->getUid(),
485 $row
486 );
487 $this->statistics['update']++;
488
489 $this->persistRelations($object, $propertyName, $this->getRelations($dataMap, $properties));
490 }
491
492 /**
493 * Deletes an object, it's 1:n related objects, and the m:n relations in relation tables (but not the m:n related objects!)
494 *
495 * @return void
496 */
497 // SK: I need to check this more thorougly
498 protected function deleteObject(Tx_ExtBase_DomainObject_DomainObjectInterface $object, $parentObject = NULL, $parentPropertyName = NULL, $recurseIntoRelations = FALSE, $onlySetDeleted = TRUE) {
499 $relations = array();
500 $properties = $object->_getDirtyProperties();
501 $dataMap = $this->getDataMap(get_class($object));
502 $relations = $this->getRelations($dataMap, $properties);
503
504 $tableName = $dataMap->getTableName();
505 if ($onlySetDeleted === TRUE && $dataMap->hasDeletedColumn()) {
506 $deletedColumnName = $dataMap->getDeletedColumnName();
507 $res = $this->database->exec_UPDATEquery(
508 $tableName,
509 'uid=' . $object->getUid(),
510 array($deletedColumnName => 1)
511 );
512 $this->statistics['update']++;
513 } else {
514 $res = $this->database->exec_DELETEquery(
515 $tableName,
516 'uid=' . $object->getUid()
517 );
518 $this->statistics['delete']++;
519 }
520
521 if ($recurseIntoRelations === TRUE) {
522 $this->processRelations($object, $propertyName, $relations);
523 }
524 }
525
526 /**
527 * Returns a table row to be inserted or updated in the database
528 *
529 * @param Tx_ExtBase_Persistence_Mapper_DataMap $dataMap The appropriate data map representing a database table
530 * @param array $properties The properties of the object
531 * @return array A single row to be inserted in the database
532 */
533 // SK: I need to check this more thorougly
534 protected function getRow(Tx_ExtBase_Persistence_Mapper_DataMap $dataMap, $properties) {
535 $relations = array();
536 foreach ($dataMap->getColumnMaps() as $columnMap) {
537 $propertyName = $columnMap->getPropertyName();
538 $columnName = $columnMap->getColumnName();
539 if ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
540 $row[$columnName] = count($properties[$propertyName]);
541 } elseif ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
542 // TODO Check if this elseif is needed or could be merged with the lines above
543 $row[$columnName] = count($properties[$propertyName]);
544 } else {
545 if ($properties[$propertyName] !== NULL) {
546 $row[$columnName] = $dataMap->convertPropertyValueToFieldValue($properties[$propertyName], FALSE);
547 }
548 }
549 }
550 return $row;
551 }
552
553 /**
554 * Returns all property values holding child objects
555 *
556 * @param Tx_ExtBase_Persistence_Mapper_DataMap $dataMap The data map
557 * @param string $properties The object properties
558 * @return array An array of properties with related child objects
559 */
560 protected function getRelations(Tx_ExtBase_Persistence_Mapper_DataMap $dataMap, $properties) {
561 $relations = array();
562 foreach ($dataMap->getColumnMaps() as $columnMap) {
563 $propertyName = $columnMap->getPropertyName();
564 $columnName = $columnMap->getColumnName();
565 if ($columnMap->isRelation()) {
566 $relations[$propertyName] = $properties[$propertyName];
567 }
568 }
569 return $relations;
570 }
571
572 /**
573 * Inserts and updates all relations of an object. It also inserts and updates data in relation tables.
574 *
575 * @param Tx_ExtBase_DomainObject_DomainObjectInterface $object The object for which the relations should be updated
576 * @param string $propertyName The name of the property holding the related child objects
577 * @param array $relations The queued relations
578 * @return void
579 */
580 protected function persistRelations(Tx_ExtBase_DomainObject_DomainObjectInterface $object, $propertyName, array $relations) {
581 $dataMap = $this->getDataMap(get_class($object));
582 foreach ($relations as $propertyName => $relatedObjects) {
583 if (!empty($relatedObjects)) {
584 $typeOfRelation = $dataMap->getColumnMap($propertyName)->getTypeOfRelation();
585 foreach ($relatedObjects as $relatedObject) {
586 if (!$this->persistenceSession->isReconstitutedObject($relatedObject)) {
587 $this->insertObject($relatedObject, $object, $propertyName);
588 if ($typeOfRelation === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
589 $this->insertRelationInRelationTable($relatedObject, $object, $propertyName);
590 }
591 } elseif ($this->persistenceSession->isReconstitutedObject($relatedObject) && $relatedObject->_isDirty()) {
592 $this->updateObject($relatedObject, $object, $propertyName);
593 }
594 }
595 }
596 }
597 }
598
599 /**
600 * Deletes all relations of an object.
601 *
602 * @param Tx_ExtBase_DomainObject_DomainObjectInterface $object The object for which the relations should be updated
603 * @param string $propertyName The name of the property holding the related child objects
604 * @param array $relations The queued relations
605 * @return void
606 */
607 protected function deleteRelations(Tx_ExtBase_DomainObject_DomainObjectInterface $object, $propertyName, array $relations) {
608 $dataMap = $this->getDataMap(get_class($object));
609 foreach ($relations as $propertyName => $relatedObjects) {
610 if (is_array($relatedObjects)) {
611 foreach ($relatedObjects as $relatedObject) {
612 $this->deleteObject($relatedObject, $object, $propertyName);
613 if ($dataMap->getColumnMap($propertyName)->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
614 $this->deleteRelationInRelationTable($relatedObject, $object, $propertyName);
615 }
616 }
617 }
618 }
619 }
620
621 /**
622 * Inserts relation to a relation table
623 *
624 * @param Tx_ExtBase_DomainObject_DomainObjectInterface $relatedObject The related object
625 * @param Tx_ExtBase_DomainObject_DomainObjectInterface $parentObject The parent object
626 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
627 * @return void
628 */
629 protected function insertRelationInRelationTable(Tx_ExtBase_DomainObject_DomainObjectInterface $relatedObject, Tx_ExtBase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
630 $dataMap = $this->getDataMap(get_class($parentObject));
631 $rowToInsert = array(
632 'uid_local' => $parentObject->getUid(),
633 'uid_foreign' => $relatedObject->getUid(),
634 'tablenames' => $dataMap->getTableName(),
635 'sorting' => 9999 // TODO sorting of mm table items
636 );
637 $tableName = $dataMap->getColumnMap($parentPropertyName)->getRelationTableName();
638 $res = $this->database->exec_INSERTquery(
639 $tableName,
640 $rowToInsert
641 );
642 $this->statistics['insert']++;
643 }
644
645 /**
646 * Update relations in a relation table
647 *
648 * @param array $relatedObjects An array of related objects
649 * @param Tx_ExtBase_DomainObject_DomainObjectInterface $parentObject The parent object
650 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
651 * @return void
652 */
653 protected function deleteRelationInRelationTable($relatedObject, Tx_ExtBase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
654 $dataMap = $this->getDataMap(get_class($parentObject));
655 $tableName = $dataMap->getColumnMap($parentPropertyName)->getRelationTableName();
656 $res = $this->database->exec_SELECTquery(
657 'uid_foreign',
658 $tableName,
659 'uid_local=' . $parentObject->getUid()
660 );
661 $this->statistics['select']++;
662 $existingRelations = array();
663 while($row = $this->database->sql_fetch_assoc($res)) {
664 $existingRelations[current($row)] = current($row);
665 }
666 $relationsToDelete = $existingRelations;
667 if (is_array($relatedObject)) {
668 foreach ($relatedObject as $relatedObject) {
669 $relatedObjectUid = $relatedObject->getUid();
670 if (array_key_exists($relatedObjectUid, $relationsToDelete)) {
671 unset($relationsToDelete[$relatedObjectUid]);
672 }
673 }
674 }
675 if (count($relationsToDelete) > 0) {
676 $relationsToDeleteList = implode(',', $relationsToDelete);
677 $res = $this->database->exec_DELETEquery(
678 $tableName,
679 'uid_local=' . $parentObject->getUid() . ' AND uid_foreign IN (' . $relationsToDeleteList . ')'
680 );
681 $this->statistics['delete']++;
682 }
683 }
684
685 /**
686 * Delegates the call to the Data Map.
687 * Returns TRUE if the property is persistable (configured in $TCA)
688 *
689 * @param string $className The property name
690 * @param string $propertyName The property name
691 * @return boolean TRUE if the property is persistable (configured in $TCA)
692 */
693 public function isPersistableProperty($className, $propertyName) {
694 $dataMap = $this->getDataMap($className);
695 return $dataMap->isPersistableProperty($propertyName);
696 }
697
698 /**
699 * Returns a data map for a given class name
700 *
701 * @return Tx_ExtBase_Persistence_Mapper_DataMap The data map
702 */
703 protected function getDataMap($className) {
704 if (empty($this->dataMaps[$className])) {
705 $dataMap = new Tx_ExtBase_Persistence_Mapper_DataMap($className);
706 $this->dataMaps[$className] = $dataMap;
707 }
708 return $this->dataMaps[$className];
709 }
710
711 public function getStatistics() {
712 return $this->statistics;
713 }
714 }
715 ?>