[+BUGFIX] Extbase (Persistence): Fixed handling of cloned object storages.
[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: Backend.php 2183 2009-04-24 14:28:37Z k-fish $
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_IdentityMap
50 **/
51 protected $identityMap;
52
53 /**
54 * @var Tx_Extbase_Reflection_Service
55 */
56 protected $reflectionService;
57
58 /**
59 * @var Tx_Extbase_Persistence_QueryFactoryInterface
60 */
61 protected $queryFactory;
62
63 /**
64 * @var Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface
65 */
66 protected $QOMFactory;
67
68 /**
69 * @var Tx_Extbase_Persistence_ValueFactoryInterface
70 */
71 protected $valueFactory;
72
73 /**
74 * @var Tx_Extbase_Persistence_Storage_BackendInterface
75 */
76 protected $storageBackend;
77
78 /**
79 * @var Tx_Extbase_Persistence_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 array
92 **/
93 protected $extbaseSettings;
94
95 /**
96 * Constructs the backend
97 *
98 * @param Tx_Extbase_Persistence_Session $session The persistence session used to persist data
99 */
100 public function __construct(Tx_Extbase_Persistence_Session $session, Tx_Extbase_Persistence_Storage_BackendInterface $storageBackend) {
101 $this->session = $session;
102 $this->storageBackend = $storageBackend;
103 $this->extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
104 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
105 $this->referenceIndex = t3lib_div::makeInstance('t3lib_refindex');
106 }
107 $this->aggregateRootObjects = new Tx_Extbase_Persistence_ObjectStorage();
108 }
109
110 /**
111 * Injects the DataMapper to map nodes to objects
112 *
113 * @param Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper
114 * @return void
115 */
116 public function injectDataMapper(Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper) {
117 $this->dataMapper = $dataMapper;
118 }
119
120 /**
121 * Injects the identity map
122 *
123 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
124 * @return void
125 */
126 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap $identityMap) {
127 $this->identityMap = $identityMap;
128 }
129
130 /**
131 * Injects the Reflection Service
132 *
133 * @param Tx_Extbase_Reflection_Service
134 * @return void
135 */
136 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
137 $this->reflectionService = $reflectionService;
138 }
139
140 /**
141 * Injects the QueryFactory
142 *
143 * @param Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory
144 * @return void
145 */
146 public function injectQueryFactory(Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory) {
147 $this->queryFactory = $queryFactory;
148 }
149
150 /**
151 * Injects the QueryObjectModelFactory
152 *
153 * @param Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface $dataMapper
154 * @return void
155 */
156 public function injectQOMFactory(Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface $QOMFactory) {
157 $this->QOMFactory = $QOMFactory;
158 }
159
160 /**
161 * Injects the ValueFactory
162 *
163 * @param Tx_Extbase_Persistence_ValueFactoryInterface $valueFactory
164 * @return void
165 */
166 public function injectValueFactory(Tx_Extbase_Persistence_ValueFactoryInterface $valueFactory) {
167 $this->valueFactory = $valueFactory;
168 }
169
170 /**
171 * Returns the repository session
172 *
173 * @return Tx_Extbase_Persistence_Session
174 */
175 public function getSession() {
176 return $this->session;
177 }
178
179 /**
180 * Returns the Data Mapper
181 *
182 * @return Tx_Extbase_Persistence_Mapper_DataMapper
183 */
184 public function getDataMapper() {
185 return $this->dataMapper;
186 }
187
188 /**
189 * Returns the current QOM factory
190 *
191 * @return Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface
192 */
193 public function getQOMFactory() {
194 return $this->QOMFactory;
195 }
196
197 /**
198 * Returns the current value factory
199 *
200 * @return Tx_Extbase_Persistence_ValueFactoryInterface
201 */
202 public function getValueFactory() {
203 return $this->valueFactory;
204 }
205
206 /**
207 * Returns the current identityMap
208 *
209 * @return Tx_Extbase_Persistence_IdentityMap
210 */
211 public function getIdentityMap() {
212 return $this->identityMap;
213 }
214
215 /**
216 * Returns the (internal) identifier for the object, if it is known to the
217 * backend. Otherwise NULL is returned.
218 *
219 * @param object $object
220 * @return string The identifier for the object if it is known, or NULL
221 */
222 public function getIdentifierByObject($object) {
223 if ($this->identityMap->hasObject($object)) {
224 return $this->identityMap->getIdentifierByObject($object);
225 } else {
226 return NULL;
227 }
228 }
229
230 /**
231 * Returns the object with the (internal) identifier, if it is known to the
232 * backend. Otherwise NULL is returned.
233 *
234 * @param string $identifier
235 * @param string $className
236 * @return object The object for the identifier if it is known, or NULL
237 */
238 public function getObjectByIdentifier($identifier, $className) {
239 if ($this->identityMap->hasIdentifier($identifier, $className)) {
240 return $this->identityMap->getObjectByIdentifier($identifier, $className);
241 } else {
242 $query = $this->queryFactory->create($className);
243 $result = $query->matching($query->withUid($identifier))->execute();
244 $object = NULL;
245 if (count($result) > 0) {
246 $object = current($result);
247 }
248 return $object;
249 }
250 }
251
252 /**
253 * Checks if the given object has ever been persisted.
254 *
255 * @param object $object The object to check
256 * @return boolean TRUE if the object is new, FALSE if the object exists in the repository
257 */
258 public function isNewObject($object) {
259 return ($this->getIdentifierByObject($object) === NULL);
260 }
261
262 /**
263 * Replaces the given object by the second object.
264 *
265 * This method will unregister the existing object at the identity map and
266 * register the new object instead. The existing object must therefore
267 * already be registered at the identity map which is the case for all
268 * reconstituted objects.
269 *
270 * The new object will be identified by the uid which formerly belonged
271 * to the existing object. The existing object looses its uid.
272 *
273 * @param object $existingObject The existing object
274 * @param object $newObject The new object
275 * @return void
276 */
277 public function replaceObject($existingObject, $newObject) {
278 $existingUid = $this->getIdentifierByObject($existingObject);
279 if ($existingUid === NULL) throw new Tx_Extbase_Persistence_Exception_UnknownObject('The given object is unknown to this persistence backend.', 1238070163);
280
281 $this->identityMap->unregisterObject($existingObject);
282 $this->identityMap->registerObject($newObject, $existingUid);
283 }
284
285 /**
286 * Sets the aggregate root objects
287 *
288 * @param Tx_Extbase_Persistence_ObjectStorage $objects
289 * @return void
290 */
291 public function setAggregateRootObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
292 $this->aggregateRootObjects = $objects;
293 }
294
295 /**
296 * Sets the deleted objects
297 *
298 * @param Tx_Extbase_Persistence_ObjectStorage $objects
299 * @return void
300 */
301 public function setDeletedObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
302 $this->deletedObjects = $objects;
303 }
304
305 /**
306 * Commits the current persistence session.
307 *
308 * @return void
309 */
310 public function commit() {
311 $this->persistObjects();
312 $this->processDeletedObjects();
313 }
314
315 /**
316 * Traverse and persist all aggregate roots and their object graph.
317 *
318 * @return void
319 */
320 protected function persistObjects() {
321 foreach ($this->aggregateRootObjects as $object) {
322 // if (!$this->identityMap->hasObject($object)) { // TODO Must be enabled to allow other identity properties than $uid
323 if ($object->_isNew()) {
324 $this->insertObject($object);
325 }
326 }
327
328 foreach ($this->aggregateRootObjects as $object) {
329 $this->persistObject($object);
330 }
331 }
332
333 /**
334 * Persists an object (instert, update) and its related objects (instert, update, delete).
335 *
336 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be inserted
337 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
338 * @param string $parentPropertyName The name of the property the object is stored in
339 * @return void
340 */
341 protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
342 $row = array();
343 $queue = array();
344 $className = get_class($object);
345 $dataMap = $this->dataMapper->getDataMap($className);
346 $classSchema = $this->reflectionService->getClassSchema($className);
347
348 $properties = $object->_getProperties();
349 foreach ($properties as $propertyName => $propertyValue) {
350 if (!$dataMap->isPersistableProperty($propertyName)) continue;
351 if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
352 continue;
353 }
354
355 $columnMap = $dataMap->getColumnMap($propertyName);
356 $propertyMetaData = $classSchema->getProperty($propertyName);
357 $propertyType = $propertyMetaData['type'];
358 // FIXME enable property-type check
359 // $this->checkPropertyType($propertyType, $propertyValue);
360 if (($propertyValue !== NULL) && ($propertyType === 'Tx_Extbase_Persistence_ObjectStorage')) {
361 if ($object->_isDirty($propertyName)) {
362 $row[$columnMap->getColumnName()] = $this->persistObjectStorage($propertyValue, $object, $propertyName, $queue);
363 } else {
364 foreach ($propertyValue as $containedObject) {
365 $queue[] = $containedObject;
366 }
367 }
368 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
369 if ($object->_isDirty($propertyName)) {
370 if ($propertyValue->_isNew()) {
371 if ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractEntity) {
372 $this->insertObject($propertyValue, $object, $propertyName);
373 $queue[] = $propertyValue;
374 } else {
375 $this->persistValueObject($propertyValue, $object, $propertyName);
376 }
377 }
378 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
379 }
380 } elseif ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject || $object->_isNew() || $object->_isDirty($propertyName)) {
381 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
382 }
383 }
384
385 if (count($row) > 0) {
386 $this->updateObject($object, $row);
387 }
388
389 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
390 $object->_memorizeCleanState();
391 }
392
393 foreach ($queue as $object) {
394 $this->persistObject($object);
395 }
396
397 }
398
399 /**
400 * Checks a value given against the expected type. If not matching, an
401 * UnexpectedTypeException is thrown. NULL is always considered valid.
402 *
403 * @param string $expectedType The expected type
404 * @param mixed $value The value to check
405 * @return void
406 * @throws Tx_Extbase_Persistence_Exception_UnexpectedType
407 */
408 protected function checkPropertyType($expectedType, $value) {
409 if ($value === NULL) {
410 return;
411 }
412
413 if (is_object($value)) {
414 if (!($value instanceof $expectedType)) {
415 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . get_class($value), 1244465558);
416 }
417 } elseif ($expectedType !== gettype($value)) {
418 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . gettype($value), 1244465558);
419 }
420 }
421
422 /**
423 * Persists the given value object.
424 *
425 * @return void
426 */
427 protected function persistValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName, $sortingPosition = 1) {
428 $result = $this->getUidOfAlreadyPersistedValueObject($object);
429 if ($result !== FALSE) {
430 $object->_setProperty('uid', (int)$result);
431 if($this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($parentPropertyName)->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
432 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
433 }
434 } else {
435 $row = array();
436 $className = get_class($object);
437 $dataMap = $this->dataMapper->getDataMap($className);
438 $classSchema = $this->reflectionService->getClassSchema($className);
439
440 $properties = $object->_getProperties();
441 foreach ($properties as $propertyName => $propertyValue) {
442 if (!$dataMap->isPersistableProperty($propertyName)) continue;
443 if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
444 continue;
445 }
446
447 $columnMap = $dataMap->getColumnMap($propertyName);
448 $propertyMetaData = $classSchema->getProperty($propertyName);
449 $propertyType = $propertyMetaData['type'];
450 // FIXME enable property-type check
451 // $this->checkPropertyType($propertyType, $propertyValue);
452 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
453 }
454 $this->insertObject($object, $parentObject, $parentPropertyName, $sortingPosition, $row);
455 }
456 }
457
458 /**
459 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
460 *
461 * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
462 */
463 protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
464 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
465 }
466
467 /**
468 * Persists a relation. Objects of a 1:n or m:n relation are queued and processed with the parent object. A 1:1 relation
469 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
470 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
471 *
472 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
473 * @param string $propertyName The name of the property the related objects are stored in
474 * @param mixed $propertyValue The property value
475 * @return void
476 */
477 protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, &$queue) {
478 $className = get_class($parentObject);
479 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
480 $columnName = $columnMap->getColumnName();
481 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
482
483 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
484 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
485 $this->removeObject($removedObject);
486 } else {
487 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
488 }
489 }
490
491 $childPidArray = array();
492 $sortingPosition = 1;
493 foreach ($objectStorage as $object) {
494 if ($object->_isNew()) {
495 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
496 $this->insertObject($object, $parentObject, $propertyName, $sortingPosition);
497 $queue[] = $object;
498 } else {
499 $this->persistValueObject($object, $parentObject, $propertyName, $sortingPosition);
500 }
501 }
502 $childPidArray[] = $object->getUid(); // FIXME This won't work for partly loaded storages
503 $sortingPosition++;
504 }
505
506 if ($columnMap->getParentKeyFieldName() === NULL) {
507 $newParentPropertyValue = implode(',', $childPidArray);
508 } else {
509 $newParentPropertyValue = count($objectStorage); // TODO check for limited queries
510 }
511
512 return $newParentPropertyValue;
513 }
514
515 /**
516 * Returns the removed objects determined by a comparison of the clean property value
517 * with the actual property value.
518 *
519 * @param Tx_Extbase_DomainObject_AbstractEntity $object The object
520 * @param string $parentPropertyName The name of the property
521 * @return array An array of removed objects
522 */
523 protected function getRemovedChildObjects(Tx_Extbase_DomainObject_AbstractEntity $object, $propertyName) {
524 $removedObjects = array();
525 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
526 $propertyValue = $object->_getProperty($propertyName);
527 if ($cleanPropertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
528 $cleanPropertyValue = $cleanPropertyValue->toArray();
529 }
530 if ($propertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
531 $propertyValue = $propertyValue->toArray();
532 }
533 if ($cleanPropertyValue instanceof Iterator) {
534 foreach ($cleanPropertyValue as $hash => $item) {
535 if (!array_key_exists($hash, $propertyValue)) {
536 $removedObjects[] = $item;
537 }
538 }
539 }
540 return $removedObjects;
541 }
542
543 /**
544 * Updates the fields defining the relation between the object and the parent object.
545 *
546 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
547 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
548 * @param string $parentPropertyName
549 * @return void
550 */
551 protected function detachObjectFromParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName) {
552 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
553 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
554 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
555 $row = array();
556 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
557 if ($parentKeyFieldName !== NULL) {
558 $row[$parentKeyFieldName] = '';
559 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
560 if ($parentTableFieldName !== NULL) {
561 $row[$parentTableFieldName] = '';
562 }
563 }
564 if (count($row) > 0) {
565 $this->updateObject($object, $row);
566 }
567 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
568 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
569 }
570 }
571
572 /**
573 * Inserts an object in the storage
574 *
575 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
576 * @param array $row The tuple to be inserted
577 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The parent object (if any)
578 * @param string $parentPropertyName The name of the property
579 */
580 protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject = NULL, $propertyName = NULL, $sortingPosition = NULL, array $row = array()) {
581 $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
582 $this->addCommonFieldsToRow($object, $row);
583 if ($parentObject !== NULL) {
584 $parentColumnMap = $this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
585 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $parentColumnMap->getParentKeyFieldName() !== NULL) {
586 $row[$parentColumnMap->getParentKeyFieldName()] = $parentObject->getUid();
587 }
588 }
589 if ($object->_isNew()) {
590 $uid = $this->storageBackend->addRow(
591 $tableName,
592 $row
593 );
594 $object->_setProperty('uid', (int)$uid);
595 }
596 if ($parentObject !== NULL) {
597 if($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
598 $this->insertRelationInRelationtable($object, $parentObject, $propertyName, $sortingPosition);
599 }
600 }
601 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
602 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
603 }
604 $this->identityMap->registerObject($object, $uid);
605 }
606
607 /**
608 * Inserts mm-relation into a relation table
609 *
610 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related object
611 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
612 * @param string $propertyName The name of the parent object's property where the related objects are stored in
613 * @return void
614 */
615 protected function insertRelationInRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $sortingPosition = NULL) {
616 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
617 $columnMap = $dataMap->getColumnMap($propertyName);
618 $row = array(
619 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
620 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
621 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
622 );
623 $relationTableName = $columnMap->getRelationTableName();
624 // FIXME Reenable support for tablenames
625 // $childTableName = $columnMap->getChildTableName();
626 // if (isset($childTableName)) {
627 // $row['tablenames'] = $childTableName;
628 // }
629 $res = $this->storageBackend->addRow(
630 $relationTableName,
631 $row,
632 TRUE);
633 return $res;
634 }
635
636 /**
637 * Delete an mm-relation from a relation table
638 *
639 * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
640 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
641 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
642 * @return void
643 */
644 protected function deleteRelationFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
645 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
646 $columnMap = $dataMap->getColumnMap($parentPropertyName);
647 $relationTableName = $columnMap->getRelationTableName();
648 $res = $this->storageBackend->removeRow(
649 $relationTableName,
650 array(
651 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
652 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(),
653 ),
654 FALSE);
655 return $res;
656 }
657
658 /**
659 * Updates a given object in the storage
660 *
661 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
662 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
663 * @param string|NULL $parentPropertyName The name of the property
664 * @param array $row The $row
665 */
666 protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
667 $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
668 $this->addCommonFieldsToRow($object, $row);
669 $uid = $object->getUid();
670 $row['uid'] = $uid;
671 $res = $this->storageBackend->updateRow(
672 $tableName,
673 $row
674 );
675 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
676 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
677 }
678 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
679 $object->_memorizeCleanState();
680 }
681 return $res;
682 }
683
684 /**
685 * Returns a table row to be inserted or updated in the database
686 *
687 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The appropriate data map representing a database table
688 * @param array $properties The properties of the object
689 * @return array A single row to be inserted in the database
690 */
691 protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
692 $className = get_class($object);
693 $dataMap = $this->dataMapper->getDataMap($className);
694 if ($dataMap->hasCreationDateColumn() && $object->_isNew()) {
695 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
696 }
697 if ($dataMap->hasTimestampColumn()) {
698 $row[$dataMap->getTimestampColumnName()] = $GLOBALS['EXEC_TIME'];
699 }
700 if ($object->_isNew() && $dataMap->hasPidColumn() && !isset($row['pid'])) {
701 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
702 }
703 }
704
705 /**
706 * Iterate over deleted aggregate root objects and process them
707 *
708 * @return void
709 */
710 protected function processDeletedObjects() {
711 foreach ($this->deletedObjects as $object) {
712 $this->removeObject($object);
713 $this->identityMap->unregisterObject($object);
714 }
715 $this->deletedObjects = new Tx_Extbase_Persistence_ObjectStorage();
716 }
717
718 /**
719 * Deletes an object
720 *
721 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
722 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
723 * @param string|NULL $parentPropertyName The name of the property
724 * @param bool $markAsDeleted Shold we only mark the row as deleted instead of deleting (TRUE by default)?
725 * @return void
726 */
727 protected function removeObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $markAsDeleted = TRUE) {
728 $dataMap = $this->dataMapper->getDataMap(get_class($object));
729 $tableName = $dataMap->getTableName();
730 if (($markAsDeleted === TRUE) && $dataMap->hasDeletedColumn()) {
731 $deletedColumnName = $dataMap->getDeletedColumnName();
732 $res = $this->storageBackend->updateRow(
733 $tableName,
734 array(
735 'uid' => $object->getUid(),
736 $deletedColumnName => 1
737 )
738 );
739 } else {
740 $res = $this->storageBackend->removeRow(
741 $tableName,
742 array('uid' => $object->getUid())
743 );
744 }
745 $this->removeRelatedObjects($object);
746 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
747 $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
748 }
749 }
750
751 /**
752 * Remove related objects
753 *
754 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to scanned for related objects
755 * @return void
756 */
757 protected function removeRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
758 $className = get_class($object);
759 $dataMap = $this->dataMapper->getDataMap($className);
760 $classSchema = $this->reflectionService->getClassSchema($className);
761
762 $properties = $object->_getProperties();
763 foreach ($properties as $propertyName => $propertyValue) {
764 $columnMap = $dataMap->getColumnMap($propertyName);
765 $propertyMetaData = $classSchema->getProperty($propertyName);
766 if ($propertyMetaData['cascade'] === 'remove') {
767 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
768 foreach ($propertyValue as $containedObject) {
769 $this->removeObject($containedObject);
770 }
771 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
772 $this->removeObject($propertyValue);
773 }
774 }
775 }
776 }
777
778 /**
779 * Delegates the call to the Data Map.
780 * Returns TRUE if the property is persistable (configured in $TCA)
781 *
782 * @param string $className The property name
783 * @param string $propertyName The property name
784 * @return boolean TRUE if the property is persistable (configured in $TCA)
785 */
786 public function isPersistableProperty($className, $propertyName) {
787 $dataMap = $this->dataMapper->getDataMap($className);
788 return $dataMap->isPersistableProperty($propertyName);
789 }
790
791 /**
792 * Determine the storage page ID for a given NEW record
793 *
794 * This does the following:
795 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
796 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
797 *
798 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
799 * @return int the storage Page ID where the object should be stored
800 */
801 protected function determineStoragePageIdForNewRecord(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
802 $className = get_class($object);
803 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
804
805 if (isset($extbaseSettings['persistence']['classes'][$className]) && !empty($extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'])) {
806 return (int)$extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'];
807 } else {
808 $storagePidList = t3lib_div::intExplode(',', $extbaseSettings['persistence']['storagePid']);
809 return (int) $storagePidList[0];
810 }
811 }
812
813 }
814
815 ?>