[+BUGFIX] Persistence: Update tstamp column on objects removal
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Backend.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 2009 Jochen Rau <jochen.rau@typoplanet.de>
6 * All rights reserved
7 *
8 * This class is a backport of the corresponding class of FLOW3.
9 * All credits go to the v5 team.
10 *
11 * This script is part of the TYPO3 project. The TYPO3 project is
12 * free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * The GNU General Public License can be found at
18 * http://www.gnu.org/copyleft/gpl.html.
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27
28 /**
29 * A persistence backend. This backend maps objects to the relational model of the storage backend.
30 * It persists all added, removed and changed objects.
31 *
32 * @package Extbase
33 * @subpackage Persistence
34 * @version $Id$
35 */
36 class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendInterface, t3lib_Singleton {
37
38 /**
39 * @var Tx_Extbase_Persistence_Session
40 */
41 protected $session;
42
43 /**
44 * @var Tx_Extbase_Persistence_ObjectStorage
45 */
46 protected $aggregateRootObjects;
47
48 /**
49 * @var Tx_Extbase_Persistence_ObjectStorage
50 */
51 protected $visitedDuringPersistence;
52
53 /**
54 * @var Tx_Extbase_Persistence_IdentityMap
55 **/
56 protected $identityMap;
57
58 /**
59 * @var Tx_Extbase_Reflection_Service
60 */
61 protected $reflectionService;
62
63 /**
64 * @var Tx_Extbase_Persistence_QueryFactoryInterface
65 */
66 protected $queryFactory;
67
68 /**
69 * @var Tx_Extbase_Persistence_QOM_QueryObjectModelFactory
70 */
71 protected $qomFactory;
72
73 /**
74 * @var Tx_Extbase_Persistence_Storage_BackendInterface
75 */
76 protected $storageBackend;
77
78 /**
79 * @var Tx_Extbase_Persistence_Mapper_DataMapper
80 */
81 protected $dataMapper;
82
83 /**
84 * The TYPO3 reference index object
85 *
86 * @var t3lib_refindex
87 **/
88 protected $referenceIndex;
89
90 /**
91 * @var Tx_Extbase_Configuration_ConfigurationManagerInterface
92 */
93 protected $configurationManager;
94
95 /**
96 * Constructs the backend
97 *
98 * @return void
99 */
100 public function __construct(Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager) {
101 $this->configurationManager = $configurationManager;
102 $frameworkConfiguration = $configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
103 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
104 $this->referenceIndex = t3lib_div::makeInstance('t3lib_refindex');
105 }
106 }
107
108 /**
109 * @param Tx_Extbase_Persistence_Session $session
110 * @return void
111 */
112 public function injectSession(Tx_Extbase_Persistence_Session $session) {
113 $this->session = $session;
114 }
115
116 /**
117 * @param Tx_Extbase_Persistence_Storage_BackendInterface $storageBackend
118 * @return void
119 */
120 public function injectStorageBackend(Tx_Extbase_Persistence_Storage_BackendInterface $storageBackend) {
121 $this->storageBackend = $storageBackend;
122 }
123
124 /**
125 * Injects the DataMapper to map nodes to objects
126 *
127 * @param Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper
128 * @return void
129 */
130 public function injectDataMapper(Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper) {
131 $this->dataMapper = $dataMapper;
132 }
133
134 /**
135 * Injects the identity map
136 *
137 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
138 * @return void
139 */
140 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap $identityMap) {
141 $this->identityMap = $identityMap;
142 }
143
144 /**
145 * Injects the Reflection Service
146 *
147 * @param Tx_Extbase_Reflection_Service
148 * @return void
149 */
150 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
151 $this->reflectionService = $reflectionService;
152 }
153
154 /**
155 * Injects the QueryFactory
156 *
157 * @param Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory
158 * @return void
159 */
160 public function injectQueryFactory(Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory) {
161 $this->queryFactory = $queryFactory;
162 }
163
164 /**
165 * Injects the QueryObjectModelFactory
166 *
167 * @param Tx_Extbase_Persistence_QOM_QueryObjectModelFactory $qomFactory
168 * @return void
169 */
170 public function injectQomFactory(Tx_Extbase_Persistence_QOM_QueryObjectModelFactory $qomFactory) {
171 $this->qomFactory = $qomFactory;
172 }
173
174 /**
175 * Returns the repository session
176 *
177 * @return Tx_Extbase_Persistence_Session
178 */
179 public function getSession() {
180 return $this->session;
181 }
182
183 /**
184 * Returns the Data Mapper
185 *
186 * @return Tx_Extbase_Persistence_Mapper_DataMapper
187 */
188 public function getDataMapper() {
189 return $this->dataMapper;
190 }
191
192 /**
193 * Returns the current QOM factory
194 *
195 * @return Tx_Extbase_Persistence_QOM_QueryObjectModelFactory
196 */
197 public function getQomFactory() {
198 return $this->qomFactory;
199 }
200
201 /**
202 * Returns the current identityMap
203 *
204 * @return Tx_Extbase_Persistence_IdentityMap
205 */
206 public function getIdentityMap() {
207 return $this->identityMap;
208 }
209
210 /**
211 * Returns the reflection service
212 *
213 * @return Tx_Extbase_Reflection_Service
214 */
215 public function getReflectionService() {
216 return $this->reflectionService;
217 }
218
219 /**
220 * Returns the number of records matching the query.
221 *
222 * @param Tx_Extbase_Persistence_QueryInterface $query
223 * @return integer
224 * @api
225 */
226 public function getObjectCountByQuery(Tx_Extbase_Persistence_QueryInterface $query) {
227 return $this->storageBackend->getObjectCountByQuery($query);
228 }
229
230 /**
231 * Returns the object data matching the $query.
232 *
233 * @param Tx_Extbase_Persistence_QueryInterface $query
234 * @return array
235 * @api
236 */
237 public function getObjectDataByQuery(Tx_Extbase_Persistence_QueryInterface $query) {
238 return $this->storageBackend->getObjectDataByQuery($query);
239 }
240
241 /**
242 * Returns the (internal) identifier for the object, if it is known to the
243 * backend. Otherwise NULL is returned.
244 *
245 * @param object $object
246 * @return string The identifier for the object if it is known, or NULL
247 */
248 public function getIdentifierByObject($object) {
249 if ($object instanceof Tx_Extbase_Persistence_LazyLoadingProxy) {
250 $object = $object->_loadRealInstance();
251 if (!is_object($object)) {
252 return NULL;
253 }
254 }
255 if ($this->identityMap->hasObject($object)) {
256 return $this->identityMap->getIdentifierByObject($object);
257 } else {
258 return NULL;
259 }
260 }
261
262 /**
263 * Returns the object with the (internal) identifier, if it is known to the
264 * backend. Otherwise NULL is returned.
265 *
266 * @param string $identifier
267 * @param string $className
268 * @return object The object for the identifier if it is known, or NULL
269 */
270 public function getObjectByIdentifier($identifier, $className) {
271 if ($this->identityMap->hasIdentifier($identifier, $className)) {
272 return $this->identityMap->getObjectByIdentifier($identifier, $className);
273 } else {
274 $query = $this->queryFactory->create($className);
275 return $query->matching(
276 $query->withUid($identifier))
277 ->execute()
278 ->getFirst();
279 }
280 }
281
282 /**
283 * Checks if the given object has ever been persisted.
284 *
285 * @param object $object The object to check
286 * @return boolean TRUE if the object is new, FALSE if the object exists in the repository
287 */
288 public function isNewObject($object) {
289 return ($this->getIdentifierByObject($object) === NULL);
290 }
291
292 /**
293 * Replaces the given object by the second object.
294 *
295 * This method will unregister the existing object at the identity map and
296 * register the new object instead. The existing object must therefore
297 * already be registered at the identity map which is the case for all
298 * reconstituted objects.
299 *
300 * The new object will be identified by the uid which formerly belonged
301 * to the existing object. The existing object looses its uid.
302 *
303 * @param object $existingObject The existing object
304 * @param object $newObject The new object
305 * @return void
306 */
307 public function replaceObject($existingObject, $newObject) {
308 $existingUid = $this->getIdentifierByObject($existingObject);
309 if ($existingUid === NULL) throw new Tx_Extbase_Persistence_Exception_UnknownObject('The given object is unknown to this persistence backend.', 1238070163);
310
311 $this->identityMap->unregisterObject($existingObject);
312 $this->identityMap->registerObject($newObject, $existingUid);
313 }
314
315 /**
316 * Sets the aggregate root objects
317 *
318 * @param Tx_Extbase_Persistence_ObjectStorage $objects
319 * @return void
320 */
321 public function setAggregateRootObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
322 $this->aggregateRootObjects = $objects;
323 }
324
325 /**
326 * Sets the deleted objects
327 *
328 * @param Tx_Extbase_Persistence_ObjectStorage $objects
329 * @return void
330 */
331 public function setDeletedObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
332 $this->deletedObjects = $objects;
333 }
334
335 /**
336 * Commits the current persistence session.
337 *
338 * @return void
339 */
340 public function commit() {
341 $this->persistObjects();
342 $this->processDeletedObjects();
343 }
344
345 /**
346 * Traverse and persist all aggregate roots and their object graph.
347 *
348 * @return void
349 */
350 protected function persistObjects() {
351 $this->visitedDuringPersistence = new Tx_Extbase_Persistence_ObjectStorage();
352 foreach ($this->aggregateRootObjects as $object) {
353 if (!$this->identityMap->hasObject($object)) {
354 $this->insertObject($object);
355 }
356 }
357 foreach ($this->aggregateRootObjects as $object) {
358 $this->persistObject($object);
359 }
360 }
361
362 /**
363 * Persists the given object.
364 *
365 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be inserted
366 * @return void
367 */
368 protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
369 if (isset($this->visitedDuringPersistence[$object])) {
370 return;
371 }
372 $row = array();
373 $queue = array();
374 $dataMap = $this->dataMapper->getDataMap(get_class($object));
375 $properties = $object->_getProperties();
376 foreach ($properties as $propertyName => $propertyValue) {
377 if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) continue;
378 $columnMap = $dataMap->getColumnMap($propertyName);
379 if ($propertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
380 if ($object->_isNew() || $propertyValue->_isDirty()) {
381 $this->persistObjectStorage($propertyValue, $object, $propertyName, $row);
382 }
383 foreach ($propertyValue as $containedObject) {
384 if ($containedObject instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
385 $queue[] = $containedObject;
386 }
387 }
388 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
389 if ($object->_isDirty($propertyName)) {
390 if ($propertyValue->_isNew()) {
391 $this->insertObject($propertyValue);
392 }
393 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
394 }
395 $queue[] = $propertyValue;
396 } elseif ($object->_isNew() || $object->_isDirty($propertyName)) {
397 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
398 }
399 }
400 if (count($row) > 0) {
401 $this->updateObject($object, $row);
402 $object->_memorizeCleanState();
403 }
404 $this->visitedDuringPersistence[$object] = $object->getUid();
405 foreach ($queue as $queuedObject) {
406 $this->persistObject($queuedObject);
407 }
408 }
409
410 /**
411 * Checks, if the property value is lazy loaded and was not initialized
412 *
413 * @param mixed $propertyValue The property value
414 * @return bool
415 */
416 protected function propertyValueIsLazyLoaded($propertyValue) {
417 if ($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) return TRUE;
418 if ($propertyValue instanceof Tx_Extbase_Persistence_LazyObjectStorage) {
419 if ($propertyValue->isInitialized() === FALSE) {
420 return TRUE;
421 }
422 }
423 return FALSE;
424 }
425
426 /**
427 * Persists a an object storage. Objects of a 1:n or m:n relation are queued and processed with the parent object. A 1:1 relation
428 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
429 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
430 *
431 * @param Tx_Extbase_Persistence_ObjectStorage $objectStorage The object storage to be persisted.
432 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object. One of the properties holds the object storage.
433 * @param string $propertyName The name of the property holding the object storage.
434 * @param array $row The row array of the parent object to be persisted. It's passed by reference and gets filled with either a comma separated list of uids (csv) or the number of contained objects.
435 * @return void
436 */
437 protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, array &$row) {
438 $className = get_class($parentObject);
439 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
440 $columnName = $columnMap->getColumnName();
441 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
442
443 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
444 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
445 $this->removeObject($removedObject);
446 } else {
447 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
448 }
449 }
450
451 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
452 $this->deleteAllRelationsFromRelationtable($parentObject, $propertyName);
453 }
454
455 $currentUids = array();
456 $sortingPosition = 1;
457 foreach ($objectStorage as $object) {
458 if ($object->_isNew()) {
459 $this->insertObject($object);
460 }
461 $currentUids[] = $object->getUid();
462 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
463 $sortingPosition++;
464 }
465
466 if ($columnMap->getParentKeyFieldName() === NULL) {
467 $row[$columnMap->getColumnName()] = implode(',', $currentUids);
468 } else {
469 $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
470 }
471 }
472
473 /**
474 * Returns the current field value of the given object property from the storage backend.
475 *
476 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
477 * @param string $propertyName The property name
478 * @return mixed The field value
479 */
480 protected function getCurrentFieldValue(Tx_Extbase_DomainObject_DomainObjectInterface $object, $propertyName) {
481 $className = get_class($object);
482 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
483 $query = $this->queryFactory->create($className);
484 $query->getQuerySettings()->setReturnRawQueryResult(TRUE);
485 $currentRow = $query->matching(
486 $query->withUid($object->getUid()))
487 ->execute()
488 ->getFirst();
489 $fieldValue = $currentRow[$columnMap->getColumnName()];
490 return $fieldValue;
491 }
492
493 /**
494 * Returns the removed objects determined by a comparison of the clean property value
495 * with the actual property value.
496 *
497 * @param Tx_Extbase_DomainObject_AbstractEntity $object The object
498 * @param string $parentPropertyName The name of the property
499 * @return array An array of removed objects
500 */
501 protected function getRemovedChildObjects(Tx_Extbase_DomainObject_AbstractEntity $object, $propertyName) {
502 $removedObjects = array();
503 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
504 if (is_array($cleanPropertyValue) || $cleanPropertyValue instanceof Iterator) {
505 $propertyValue = $object->_getProperty($propertyName);
506 foreach ($cleanPropertyValue as $containedObject) {
507 if (!$propertyValue->contains($containedObject)) {
508 $removedObjects[] = $containedObject;
509 }
510 }
511 }
512 return $removedObjects;
513 }
514
515 /**
516 * Updates the fields defining the relation between the object and the parent object.
517 *
518 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
519 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
520 * @param string $parentPropertyName
521 * @return void
522 */
523 protected function attachObjectToParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0) {
524 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
525 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
526 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
527 $row = array();
528 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
529 if ($parentKeyFieldName !== NULL) {
530 $row[$parentKeyFieldName] = $parentObject->getUid();
531 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
532 if ($parentTableFieldName !== NULL) {
533 $row[$parentTableFieldName] = $parentDataMap->getTableName();
534 }
535 }
536 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
537 if (!empty($childSortByFieldName)) {
538 $row[$childSortByFieldName] = $sortingPosition;
539 }
540 if (count($row) > 0) {
541 $this->updateObject($object, $row);
542 }
543 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
544 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
545 }
546 }
547
548 /**
549 * Updates the fields defining the relation between the object and the parent object.
550 *
551 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
552 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
553 * @param string $parentPropertyName
554 * @return void
555 */
556 protected function detachObjectFromParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName) {
557 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
558 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
559 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
560 $row = array();
561 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
562 if ($parentKeyFieldName !== NULL) {
563 $row[$parentKeyFieldName] = '';
564 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
565 if ($parentTableFieldName !== NULL) {
566 $row[$parentTableFieldName] = '';
567 }
568 }
569 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
570 if (!empty($childSortByFieldName)) {
571 $row[$childSortByFieldName] = 0;
572 }
573 if (count($row) > 0) {
574 $this->updateObject($object, $row);
575 }
576 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
577 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
578 }
579 }
580
581 /**
582 * Inserts an object in the storage backend
583 *
584 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
585 * @return void
586 */
587 protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
588 if ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject) {
589 $result = $this->getUidOfAlreadyPersistedValueObject($object);
590 if ($result !== FALSE) {
591 $object->_setProperty('uid', (int)$result);
592 return;
593 }
594 }
595
596 $dataMap = $this->dataMapper->getDataMap(get_class($object));
597 $row = array();
598 $this->addCommonFieldsToRow($object, $row);
599 if($dataMap->getLanguageIdColumnName() !== NULL) {
600 $row[$dataMap->getLanguageIdColumnName()] = -1;
601 }
602 $uid = $this->storageBackend->addRow(
603 $dataMap->getTableName(),
604 $row
605 );
606 $object->_setProperty('uid', (int)$uid);
607 $frameworkConfiguration = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
608 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
609 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $uid);
610 }
611 $this->identityMap->registerObject($object, $uid);
612 }
613
614 /**
615 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
616 *
617 * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
618 * @return mixed The matching uid if an object was found, else FALSE
619 */
620 protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
621 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
622 }
623
624 /**
625 * Inserts mm-relation into a relation table
626 *
627 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related object
628 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
629 * @param string $propertyName The name of the parent object's property where the related objects are stored in
630 * @param int $sortingPosition Defaults to NULL
631 * @return int The uid of the inserted row
632 */
633 protected function insertRelationInRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $sortingPosition = NULL) {
634 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
635 $columnMap = $dataMap->getColumnMap($propertyName);
636 $row = array(
637 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
638 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
639 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
640 );
641 $relationTableName = $columnMap->getRelationTableName();
642 // FIXME Reenable support for tablenames
643 // $childTableName = $columnMap->getChildTableName();
644 // if (isset($childTableName)) {
645 // $row['tablenames'] = $childTableName;
646 // }
647 if ($columnMap->getRelationTablePageIdColumnName() !== NULL) {
648 $row[$columnMap->getRelationTablePageIdColumnName()] = $this->determineStoragePageIdForNewRecord();
649 }
650
651 $relationTableInsertFields = $columnMap->getRelationTableInsertFields();
652 if (count($relationTableInsertFields)) {
653 foreach($relationTableInsertFields as $insertField => $insertValue) {
654 $row[$insertField] = $insertValue;
655 }
656 }
657
658 $res = $this->storageBackend->addRow(
659 $relationTableName,
660 $row,
661 TRUE);
662 return $res;
663 }
664
665 /**
666 * Delete all mm-relations of a parent from a relation table
667 *
668 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
669 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
670 * @return bool
671 */
672 protected function deleteAllRelationsFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
673 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
674 $columnMap = $dataMap->getColumnMap($parentPropertyName);
675 $relationTableName = $columnMap->getRelationTableName();
676
677 $relationMatchFields = array(
678 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
679 );
680
681 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
682 if (is_array($relationTableMatchFields) && count($relationTableMatchFields) > 0) {
683 $relationMatchFields = array_merge($relationTableMatchFields,$relationMatchFields);
684 }
685
686 $res = $this->storageBackend->removeRow(
687 $relationTableName,
688 $relationMatchFields,
689 FALSE);
690 return $res;
691 }
692
693 /**
694 * Delete an mm-relation from a relation table
695 *
696 * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
697 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
698 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
699 * @return bool
700 */
701 protected function deleteRelationFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
702 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
703 $columnMap = $dataMap->getColumnMap($parentPropertyName);
704 $relationTableName = $columnMap->getRelationTableName();
705 $res = $this->storageBackend->removeRow(
706 $relationTableName,
707 array(
708 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
709 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(),
710 ),
711 FALSE);
712 return $res;
713 }
714
715 /**
716 * Updates a given object in the storage
717 *
718 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be updated
719 * @param array $row Row to be stored
720 * @return bool
721 */
722 protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $row) {
723 $dataMap = $this->dataMapper->getDataMap(get_class($object));
724 $this->addCommonFieldsToRow($object, $row);
725 $row['uid'] = $object->getUid();
726 if($dataMap->getLanguageIdColumnName() !== NULL) {
727 $row[$dataMap->getLanguageIdColumnName()] = $object->_getProperty('_languageUid');
728 if ($object->_getProperty('_localizedUid') !== NULL) {
729 $row['uid'] = $object->_getProperty('_localizedUid');
730 }
731 }
732 $res = $this->storageBackend->updateRow(
733 $dataMap->getTableName(),
734 $row
735 );
736 $frameworkConfiguration = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
737 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
738 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $row['uid']);
739 }
740 return $res;
741 }
742
743 /**
744 * Adds common databse fields to a row
745 *
746 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
747 * @param array $row
748 * @return void
749 */
750 protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
751 $dataMap = $this->dataMapper->getDataMap(get_class($object));
752 $this->addCommonDateFieldsToRow($object, $row);
753 if ($dataMap->getRecordTypeColumnName() !== NULL && $dataMap->getRecordType() !== NULL) {
754 $row[$dataMap->getRecordTypeColumnName()] = $dataMap->getRecordType();
755 }
756 if ($object->_isNew() && !isset($row['pid'])) {
757 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
758 }
759 }
760
761 /**
762 * Adjustes the common date fields of the given row to the current time
763 *
764 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
765 * @param array $row The row to be updated
766 * @return void
767 */
768 protected function addCommonDateFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
769 $dataMap = $this->dataMapper->getDataMap(get_class($object));
770 if ($object->_isNew() && $dataMap->getCreationDateColumnName() !== NULL) {
771 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
772 }
773 if ($dataMap->getModificationDateColumnName() !== NULL) {
774 $row[$dataMap->getModificationDateColumnName()] = $GLOBALS['EXEC_TIME'];
775 }
776 }
777
778 /**
779 * Iterate over deleted aggregate root objects and process them
780 *
781 * @return void
782 */
783 protected function processDeletedObjects() {
784 foreach ($this->deletedObjects as $object) {
785 $this->removeObject($object);
786 $this->identityMap->unregisterObject($object);
787 }
788 $this->deletedObjects = new Tx_Extbase_Persistence_ObjectStorage();
789 }
790
791 /**
792 * Deletes an object
793 *
794 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be removed from the storage
795 * @param bool $markAsDeleted Wether to just flag the row deleted (default) or really delete it
796 * @return void
797 */
798 protected function removeObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $markAsDeleted = TRUE) {
799 $dataMap = $this->dataMapper->getDataMap(get_class($object));
800 $tableName = $dataMap->getTableName();
801 if (($markAsDeleted === TRUE) && ($dataMap->getDeletedFlagColumnName() !== NULL)) {
802 $deletedColumnName = $dataMap->getDeletedFlagColumnName();
803 $row = array(
804 'uid' => $object->getUid(),
805 $deletedColumnName => 1
806 );
807 $this->addCommonDateFieldsToRow($object, $row);
808 $res = $this->storageBackend->updateRow(
809 $tableName,
810 $row
811 );
812 } else {
813 $res = $this->storageBackend->removeRow(
814 $tableName,
815 array('uid' => $object->getUid())
816 );
817 }
818 $this->removeRelatedObjects($object);
819 $frameworkConfiguration = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
820 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
821 $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
822 }
823 }
824
825 /**
826 * Remove related objects
827 *
828 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to scanned for related objects
829 * @return void
830 */
831 protected function removeRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
832 $className = get_class($object);
833 $dataMap = $this->dataMapper->getDataMap($className);
834 $classSchema = $this->reflectionService->getClassSchema($className);
835
836 $properties = $object->_getProperties();
837 foreach ($properties as $propertyName => $propertyValue) {
838 $columnMap = $dataMap->getColumnMap($propertyName);
839 $propertyMetaData = $classSchema->getProperty($propertyName);
840 if ($propertyMetaData['cascade'] === 'remove') {
841 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
842 foreach ($propertyValue as $containedObject) {
843 $this->removeObject($containedObject);
844 }
845 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
846 $this->removeObject($propertyValue);
847 }
848 }
849 }
850 }
851
852 /**
853 * Determine the storage page ID for a given NEW record
854 *
855 * This does the following:
856 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
857 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
858 *
859 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
860 * @return int the storage Page ID where the object should be stored
861 */
862 protected function determineStoragePageIdForNewRecord(Tx_Extbase_DomainObject_DomainObjectInterface $object = NULL) {
863 $frameworkConfiguration = $this->configurationManager->getConfiguration(Tx_Extbase_Configuration_ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
864 if ($object !== NULL) {
865 $className = get_class($object);
866 if (isset($frameworkConfiguration['persistence']['classes'][$className]) && !empty($frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'])) {
867 return (int)$frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'];
868 }
869 }
870 $storagePidList = t3lib_div::intExplode(',', $frameworkConfiguration['persistence']['storagePid']);
871 return (int) $storagePidList[0];
872 }
873
874 /**
875 * Returns a plain value, i.e. objects are flattened out if possible.
876 *
877 * @param mixed $input
878 * @return mixed
879 */
880 protected function getPlainValue($input) {
881 if ($input instanceof DateTime) {
882 return $input->format('U');
883 } elseif ($input instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
884 return $input->getUid();
885 } elseif (is_bool($input)) {
886 return $input === TRUE ? 1 : 0;
887 } else {
888 return $input;
889 }
890 }
891
892 }
893
894 ?>