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