185f56201c01de47980bde14dfe8aa3fd7cdf33c
[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 '*', // TODO limit fetched fields (CH: should we do that? JR: Not needed; only existing properties will be mapped)
202 $from,
203 $where . $enableFields,
204 $groupBy,
205 $orderBy,
206 $limit
207 );
208
209 $fieldMap = $this->getFieldMapFromResult($res);
210 $rows = $this->getRowsFromResult($res);
211
212 $this->database->sql_free_result($res);
213
214 // SK: Do we want to make it possible to ignore "enableFields"?
215 // TODO language overlay; workspace overlay
216 $objects = array();
217 if (is_array($rows)) {
218 if (count($rows) > 0) {
219 $objects = $this->reconstituteObjects($dataMap, $fieldMap, $rows);
220 }
221 }
222 return $objects;
223 }
224
225 protected function getFieldMapFromResult($res) {
226 $fieldMap = array();
227 if ($res !== FALSE) {
228 $fieldPosition = 0;
229 // FIXME mysql_fetch_field should be available in t3lib_db (patch core)
230 while ($field = mysql_fetch_field($res)) {
231 $fieldMap[$field->table][$field->name] = $fieldPosition;
232 $fieldPosition++;
233 }
234 }
235 return $fieldMap;
236 }
237
238 protected function getRowsFromResult($res) {
239 $rows = array();
240 if ($res !== FALSE) {
241 while($rows[] = $this->database->sql_fetch_row($res));
242 array_pop($rows);
243 }
244 return $rows;
245 }
246
247 /**
248 * Get the join clause for the fetch method for a specific class. This will
249 * eagerly load all has-one relations.
250 *
251 * @param string $className The class name
252 * @return string The join clause
253 */
254 protected function getJoinClause($className) {
255 $dataMap = $this->getDataMap($className);
256 $join = '';
257 foreach ($dataMap->getColumnMaps() as $propertyName => $columnMap) {
258 if ($columnMap->getTypeOfRelation() == Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
259 $join .= ' LEFT JOIN ' . $columnMap->getChildTableName() . ' ON ' . $dataMap->getTableName() . '.' . $columnMap->getColumnName() . ' = ' . $columnMap->getChildTableName() . '.uid';
260 $join .= $this->getJoinClause($columnMap->getChildClassName());
261 }
262 }
263 return $join;
264 }
265
266 /**
267 * Fetches a rows from the database by given SQL statement snippets taking a relation table into account
268 *
269 * @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!
270 * @param string Optional GROUP BY field(s), defaults to blank string.
271 * @param string Optional ORDER BY field(s), defaults to blank string.
272 * @param string Optional LIMIT value ([begin,]max), defaults to blank string.
273 */
274 public function fetchWithRelationTable($parentObject, $columnMap, $where = '', $groupBy = '', $orderBy = '', $limit = '', $useEnableFields = TRUE) {
275 if (!strlen($where)) {
276 $where = '1=1';
277 }
278 $from = $columnMap->getChildTableName() . ' LEFT JOIN ' . $columnMap->getRelationTableName() . ' ON (' . $columnMap->getChildTableName() . '.uid=' . $columnMap->getRelationTableName() . '.uid_foreign)';
279 $where .= ' AND ' . $columnMap->getRelationTableName() . '.uid_local=' . t3lib_div::intval_positive($parentObject->getUid());
280
281 return $this->fetch($columnMap->getChildClassName(), $where, $from, $groupBy, $orderBy, $limit, $useEnableFields);
282 }
283
284 /**
285 * reconstitutes domain objects from $rows (array)
286 *
287 * @param Tx_ExtBase_Persistence_Mapper_DataMap $dataMap The data map corresponding to the domain object
288 * @param array $fieldMap An array indexed by the table name and field name to the row index
289 * @param array $rows The rows array fetched from the database (not associative)
290 * @return array An array of reconstituted domain objects
291 */
292 // SK: I Need to check this method more thoroughly.
293 // SK: Are loops detected during reconstitution?
294 protected function reconstituteObjects($dataMap, &$fieldMap, array $rows) {
295 $objects = array();
296 foreach ($rows as $row) {
297 $properties = array();
298 foreach ($dataMap->getColumnMaps() as $columnMap) {
299 $fieldValue = $row[$fieldMap[$dataMap->getTableName()][$columnMap->getColumnName()]];
300 $properties[$columnMap->getPropertyName()] = $dataMap->convertFieldValueToPropertyValue($columnMap->getPropertyName(), $fieldValue);
301 }
302 $object = $this->reconstituteObject($dataMap->getClassName(), $properties);
303 foreach ($dataMap->getColumnMaps() as $columnMap) {
304 if ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_ONE) {
305 list($relatedObject) = $this->reconstituteObjects($this->getDataMap($columnMap->getChildClassName()), $fieldMap, array($row));
306 $object->_reconstituteProperty($columnMap->getPropertyName(), $relatedObject);
307 } elseif ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
308 $where = $columnMap->getParentKeyFieldName() . '=' . intval($object->getUid());
309 $relatedDataMap = $this->getDataMap($columnMap->getChildClassName());
310 $relatedObjects = $this->fetch($columnMap->getChildClassName(), $where);
311 $object->_reconstituteProperty($columnMap->getPropertyName(), $relatedObjects);
312 } elseif ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
313 $relatedDataMap = $this->getDataMap($columnMap->getChildClassName());
314 $relatedObjects = $this->fetchWithRelationTable($object, $columnMap);
315 $object->_reconstituteProperty($columnMap->getPropertyName(), $relatedObjects);
316 }
317 }
318 $this->persistenceSession->registerReconstitutedObject($object);
319 $objects[] = $object;
320 }
321 return $objects;
322 }
323
324 /**
325 * Reconstitutes the specified object and fills it with the given properties.
326 *
327 * @param string $objectName Name of the object to reconstitute
328 * @param array $properties The names of properties and their values which should be set during the reconstitution
329 * @return object The reconstituted object
330 */
331 protected function reconstituteObject($className, array $properties = array()) {
332 // those objects will be fetched from within the __wakeup() method of the object...
333 $GLOBALS['ExtBase']['reconstituteObject']['properties'] = $properties;
334 $object = unserialize('O:' . strlen($className) . ':"' . $className . '":0:{};');
335 unset($GLOBALS['ExtBase']['reconstituteObject']);
336 return $object;
337 }
338
339 /**
340 * Persists all objects of a persistence session
341 *
342 * @return void
343 */
344 public function persistAll() {
345 // first, persit all aggregate root objects
346 $aggregateRootClassNames = $this->persistenceSession->getAggregateRootClassNames();
347 foreach ($aggregateRootClassNames as $className) {
348 $this->persistObjects($className);
349 }
350 // persist all remaining objects registered manually
351 // $this->persistObjects();
352 }
353
354 /**
355 * Persists all objects of a persitance persistence session that are of a given class. If there
356 * is no class specified, it persits all objects of a persistence session.
357 *
358 * @param string $className Name of the class of the objects to be persisted
359 */
360 protected function persistObjects($className = NULL) {
361 foreach ($this->persistenceSession->getAddedObjects($className) as $object) {
362 $this->insertObject($object);
363 $this->persistenceSession->unregisterObject($object);
364 $this->persistenceSession->registerReconstitutedObject($object);
365 }
366 foreach ($this->persistenceSession->getDirtyObjects($className) as $object) {
367 $this->updateObject($object);
368 $this->persistenceSession->unregisterObject($object);
369 $this->persistenceSession->registerReconstitutedObject($object);
370 }
371 foreach ($this->persistenceSession->getRemovedObjects($className) as $object) {
372 $this->deleteObject($object);
373 $this->persistenceSession->unregisterObject($object);
374 }
375 }
376
377 /**
378 * Inserts an object to the database.
379 *
380 * @return void
381 */
382 // SK: I need to check this more thorougly
383 protected function insertObject(Tx_ExtBase_DomainObject_AbstractDomainObject $object, $parentObject = NULL, $parentPropertyName = NULL, $recurseIntoRelations = TRUE) {
384 $properties = $object->_getProperties();
385 $dataMap = $this->getDataMap(get_class($object));
386 $row = $this->getRow($dataMap, $properties);
387
388 if ($parentObject instanceof Tx_ExtBase_DomainObject_AbstractDomainObject && $parentPropertyName !== NULL) {
389 $parentDataMap = $this->getDataMap(get_class($parentObject));
390 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
391 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
392 if ($parentKeyFieldName !== NULL) {
393 $row[$parentKeyFieldName] = $parentObject->getUid();
394 }
395 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
396 if ($parentTableFieldName !== NULL) {
397 $row[$parentTableFieldName] = $parentDataMap->getTableName();
398 }
399 }
400
401 unset($row['uid']);
402
403 $row['pid'] = !empty($this->cObj->data['pages']) ? $this->cObj->data['pages'] : $GLOBALS['TSFE']->id;
404 $row['tstamp'] = time();
405
406 $tableName = $dataMap->getTableName();
407 $res = $this->database->exec_INSERTquery(
408 $tableName,
409 $row
410 );
411 $object->_reconstituteProperty('uid', $this->database->sql_insert_id());
412
413 $this->persistRelations($object, $propertyName, $this->getRelations($dataMap, $properties));
414 }
415
416 /**
417 * Updates a modified object in the database
418 *
419 * @return void
420 */
421 // SK: I need to check this more thorougly
422 protected function updateObject(Tx_ExtBase_DomainObject_AbstractDomainObject $object, $parentObject = NULL, $parentPropertyName = NULL, $recurseIntoRelations = TRUE) {
423 $properties = $object->_getDirtyProperties();
424 $dataMap = $this->getDataMap(get_class($object));
425 $row = $this->getRow($dataMap, $properties);
426 unset($row['uid']);
427 // TODO Check for crdate column
428 $row['crdate'] = time();
429 if (!empty($GLOBALS['TSFE']->fe_user->user['uid'])) {
430 $row['cruser_id'] = $GLOBALS['TSFE']->fe_user->user['uid'];
431 }
432 if ($parentObject instanceof Tx_ExtBase_DomainObject_AbstractDomainObject && $parentPropertyName !== NULL) {
433 $parentDataMap = $this->getDataMap(get_class($parentObject));
434 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
435 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
436 if ($parentKeyFieldName !== NULL) {
437 $row[$parentKeyFieldName] = $parentObject->getUid();
438 }
439 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
440 if ($parentTableFieldName !== NULL) {
441 $row[$parentTableFieldName] = $parentDataMap->getTableName();
442 }
443 }
444
445 $tableName = $dataMap->getTableName();
446 $res = $this->database->exec_UPDATEquery(
447 $tableName,
448 'uid=' . $object->getUid(),
449 $row
450 );
451
452 $this->persistRelations($object, $propertyName, $this->getRelations($dataMap, $properties));
453 }
454
455 /**
456 * Deletes an object, it's 1:n related objects, and the m:n relations in relation tables (but not the m:n related objects!)
457 *
458 * @return void
459 */
460 // SK: I need to check this more thorougly
461 protected function deleteObject(Tx_ExtBase_DomainObject_AbstractDomainObject $object, $parentObject = NULL, $parentPropertyName = NULL, $recurseIntoRelations = FALSE, $onlySetDeleted = TRUE) {
462 $relations = array();
463 $properties = $object->_getDirtyProperties();
464 $dataMap = $this->getDataMap(get_class($object));
465 $relations = $this->getRelations($dataMap, $properties);
466
467 $tableName = $dataMap->getTableName();
468 if ($onlySetDeleted === TRUE && !empty($deletedColumnName)) {
469 $deletedColumnName = $dataMap->getDeletedColumnName();
470 $res = $this->database->exec_UPDATEquery(
471 $tableName,
472 'uid=' . $object->getUid(),
473 array($deletedColumnName => 1)
474 );
475 } else {
476 $res = $this->database->exec_DELETEquery(
477 $tableName,
478 'uid=' . $object->getUid()
479 );
480 }
481
482 if ($recurseIntoRelations === TRUE) {
483 $this->processRelations($object, $propertyName, $relations);
484 }
485 }
486
487 /**
488 * Returns a table row to be inserted or updated in the database
489 *
490 * @param Tx_ExtBase_Persistence_Mapper_DataMap $dataMap The appropriate data map representing a database table
491 * @param array $properties The properties of the object
492 * @return array A single row to be inserted in the database
493 */
494 // SK: I need to check this more thorougly
495 protected function getRow(Tx_ExtBase_Persistence_Mapper_DataMap $dataMap, $properties) {
496 $relations = array();
497 foreach ($dataMap->getColumnMaps() as $columnMap) {
498 $propertyName = $columnMap->getPropertyName();
499 $columnName = $columnMap->getColumnName();
500 if ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
501 $row[$columnName] = count($properties[$propertyName]);
502 } elseif ($columnMap->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
503 // TODO Check if this elseif is needed or could be merged with the lines above
504 $row[$columnName] = count($properties[$propertyName]);
505 } else {
506 if ($properties[$propertyName] !== NULL) {
507 $row[$columnName] = $dataMap->convertPropertyValueToFieldValue($properties[$propertyName], FALSE);
508 }
509 }
510 }
511 return $row;
512 }
513
514 /**
515 * Returns all property values holding child objects
516 *
517 * @param Tx_ExtBase_Persistence_Mapper_DataMap $dataMap The data map
518 * @param string $properties The object properties
519 * @return array An array of properties with related child objects
520 */
521 protected function getRelations(Tx_ExtBase_Persistence_Mapper_DataMap $dataMap, $properties) {
522 $relations = array();
523 foreach ($dataMap->getColumnMaps() as $columnMap) {
524 $propertyName = $columnMap->getPropertyName();
525 $columnName = $columnMap->getColumnName();
526 if ($columnMap->isRelation()) {
527 $relations[$propertyName] = $properties[$propertyName];
528 }
529 }
530 return $relations;
531 }
532
533 /**
534 * Inserts and updates all relations of an object. It also inserts and updates data in relation tables.
535 *
536 * @param Tx_ExtBase_DomainObject_AbstractDomainObject $object The object for which the relations should be updated
537 * @param string $propertyName The name of the property holding the related child objects
538 * @param array $relations The queued relations
539 * @return void
540 */
541 protected function persistRelations(Tx_ExtBase_DomainObject_AbstractDomainObject $object, $propertyName, array $relations) {
542 $dataMap = $this->getDataMap(get_class($object));
543 foreach ($relations as $propertyName => $relatedObjects) {
544 if (!empty($relatedObjects)) {
545 $typeOfRelation = $dataMap->getColumnMap($propertyName)->getTypeOfRelation();
546 foreach ($relatedObjects as $relatedObject) {
547 if (!$this->persistenceSession->isReconstitutedObject($relatedObject)) {
548 $this->insertObject($relatedObject, $object, $propertyName);
549 if ($typeOfRelation === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
550 $this->insertRelationInRelationTable($relatedObject, $object, $propertyName);
551 }
552 } elseif ($this->persistenceSession->isReconstitutedObject($relatedObject) && $relatedObject->_isDirty()) {
553 $this->updateObject($relatedObject, $object, $propertyName);
554 }
555 }
556 }
557 }
558 }
559
560 /**
561 * Deletes all relations of an object.
562 *
563 * @param Tx_ExtBase_DomainObject_AbstractDomainObject $object The object for which the relations should be updated
564 * @param string $propertyName The name of the property holding the related child objects
565 * @param array $relations The queued relations
566 * @return void
567 */
568 protected function deleteRelations(Tx_ExtBase_DomainObject_AbstractDomainObject $object, $propertyName, array $relations) {
569 $dataMap = $this->getDataMap(get_class($object));
570 foreach ($relations as $propertyName => $relatedObjects) {
571 if (is_array($relatedObjects)) {
572 foreach ($relatedObjects as $relatedObject) {
573 $this->deleteObject($relatedObject, $object, $propertyName);
574 if ($dataMap->getColumnMap($propertyName)->getTypeOfRelation() === Tx_ExtBase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
575 $this->deleteRelationInRelationTable($relatedObject, $object, $propertyName);
576 }
577 }
578 }
579 }
580 }
581
582 /**
583 * Inserts relation to a relation table
584 *
585 * @param Tx_ExtBase_DomainObject_AbstractDomainObject $relatedObject The related object
586 * @param Tx_ExtBase_DomainObject_AbstractDomainObject $parentObject The parent object
587 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
588 * @return void
589 */
590 protected function insertRelationInRelationTable(Tx_ExtBase_DomainObject_AbstractDomainObject $relatedObject, Tx_ExtBase_DomainObject_AbstractDomainObject $parentObject, $parentPropertyName) {
591 $dataMap = $this->getDataMap(get_class($parentObject));
592 $rowToInsert = array(
593 'uid_local' => $parentObject->getUid(),
594 'uid_foreign' => $relatedObject->getUid(),
595 'tablenames' => $dataMap->getTableName(),
596 'sorting' => 9999 // TODO sorting of mm table items
597 );
598 $tableName = $dataMap->getColumnMap($parentPropertyName)->getRelationTableName();
599 $res = $this->database->exec_INSERTquery(
600 $tableName,
601 $rowToInsert
602 );
603 }
604
605 /**
606 * Update relations in a relation table
607 *
608 * @param array $relatedObjects An array of related objects
609 * @param Tx_ExtBase_DomainObject_AbstractDomainObject $parentObject The parent object
610 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
611 * @return void
612 */
613 protected function deleteRelationInRelationTable($relatedObject, Tx_ExtBase_DomainObject_AbstractDomainObject $parentObject, $parentPropertyName) {
614 $dataMap = $this->getDataMap(get_class($parentObject));
615 $tableName = $dataMap->getColumnMap($parentPropertyName)->getRelationTableName();
616 $res = $this->database->exec_SELECTquery(
617 'uid_foreign',
618 $tableName,
619 'uid_local=' . $parentObject->getUid()
620 );
621 $existingRelations = array();
622 while($row = $this->database->sql_fetch_assoc($res)) {
623 $existingRelations[current($row)] = current($row);
624 }
625 $relationsToDelete = $existingRelations;
626 if (is_array($relatedObject)) {
627 foreach ($relatedObject as $relatedObject) {
628 $relatedObjectUid = $relatedObject->getUid();
629 if (array_key_exists($relatedObjectUid, $relationsToDelete)) {
630 unset($relationsToDelete[$relatedObjectUid]);
631 }
632 }
633 }
634 if (count($relationsToDelete) > 0) {
635 $relationsToDeleteList = implode(',', $relationsToDelete);
636 $res = $this->database->exec_DELETEquery(
637 $tableName,
638 'uid_local=' . $parentObject->getUid() . ' AND uid_foreign IN (' . $relationsToDeleteList . ')'
639 );
640 }
641 }
642
643 /**
644 * Delegates the call to the Data Map.
645 * Returns TRUE if the property is persistable (configured in $TCA)
646 *
647 * @param string $className The property name
648 * @param string $propertyName The property name
649 * @return boolean TRUE if the property is persistable (configured in $TCA)
650 */
651 public function isPersistableProperty($className, $propertyName) {
652 $dataMap = new Tx_ExtBase_Persistence_Mapper_DataMap($className);
653 $dataMap->initialize();
654 return $dataMap->isPersistableProperty($propertyName);
655 return $dataMap->isPersistableProperty($propertyName);
656 }
657
658 /**
659 * Returns a data map for a given class name
660 *
661 * @return Tx_ExtBase_Persistence_Mapper_DataMap The data map
662 */
663 protected function getDataMap($className) {
664 if (empty($this->dataMaps[$className])) {
665 $dataMap = new Tx_ExtBase_Persistence_Mapper_DataMap($className);
666 $dataMap->initialize();
667 $this->dataMaps[$className] = $dataMap;
668 }
669 return $this->dataMaps[$className];
670 }
671
672 }
673 ?>