Add Extbase 1.0.1 to TYPO3core. Do NOT make changes inside! See misc/core_svn_rules...
[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 1729 2009-11-25 21:37:20Z stucki $
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)) {
323 $this->insertObject($object);
324 }
325 }
326 foreach ($this->aggregateRootObjects as $object) {
327 $this->persistObject($object);
328 }
329 }
330
331 /**
332 * Persists an object (instert, update) and its related objects (instert, update, delete).
333 *
334 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be inserted
335 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
336 * @param string $parentPropertyName The name of the property the object is stored in
337 * @return void
338 */
339 protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
340 $row = array();
341 $queue = array();
342 $className = get_class($object);
343 $dataMap = $this->dataMapper->getDataMap($className);
344 $classSchema = $this->reflectionService->getClassSchema($className);
345
346 $properties = $object->_getProperties();
347 foreach ($properties as $propertyName => $propertyValue) {
348 if (!$dataMap->isPersistableProperty($propertyName)) continue;
349 if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
350 continue;
351 }
352
353 $columnMap = $dataMap->getColumnMap($propertyName);
354 $childClassName = $columnMap->getChildClassName();
355 $propertyMetaData = $classSchema->getProperty($propertyName);
356 $propertyType = $propertyMetaData['type'];
357 // FIXME enable property-type check
358 // $this->checkPropertyType($propertyType, $propertyValue);
359 if (($propertyValue !== NULL) && ($propertyValue instanceof Tx_Extbase_Persistence_ObjectStorage || $propertyType === 'Tx_Extbase_Persistence_ObjectStorage')) {
360 if ($object->_isNew() || $object->_isDirty($propertyName)) {
361 $this->persistObjectStorage($propertyValue, $object, $propertyName, $queue, $row);
362 }
363 foreach ($propertyValue as $containedObject) {
364 if ($containedObject instanceof Tx_Extbase_DomainObject_AbstractEntity) {
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);
373 $queue[] = $propertyValue;
374 } else {
375 $this->persistValueObject($propertyValue);
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 $queuedObject) {
394 $this->persistObject($queuedObject);
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, $sortingPosition = 1) {
428 $result = $this->getUidOfAlreadyPersistedValueObject($object);
429 if ($result !== FALSE) {
430 $object->_setProperty('uid', (int)$result);
431 } elseif ($object->_isNew()) {
432 $row = array();
433 $className = get_class($object);
434 $dataMap = $this->dataMapper->getDataMap($className);
435 $classSchema = $this->reflectionService->getClassSchema($className);
436
437 $properties = $object->_getProperties();
438 foreach ($properties as $propertyName => $propertyValue) {
439 if (!$dataMap->isPersistableProperty($propertyName)) continue;
440 if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
441 continue;
442 }
443
444 $columnMap = $dataMap->getColumnMap($propertyName);
445 $propertyMetaData = $classSchema->getProperty($propertyName);
446 $propertyType = $propertyMetaData['type'];
447 // FIXME enable property-type check
448 // $this->checkPropertyType($propertyType, $propertyValue);
449 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
450 }
451 $this->insertObject($object, $row);
452 }
453 }
454
455 /**
456 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
457 *
458 * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
459 */
460 protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
461 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
462 }
463
464 /**
465 * 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
466 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
467 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
468 *
469 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
470 * @param string $propertyName The name of the property the related objects are stored in
471 * @param mixed $propertyValue The property value
472 * @return void
473 */
474 protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, array &$queue, array &$row) {
475 $className = get_class($parentObject);
476 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
477 $columnName = $columnMap->getColumnName();
478 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
479
480 $currentUids = array();
481 if ($columnMap->getParentKeyFieldName() === NULL) {
482 $currentFieldValue = $this->getCurrentFieldValue($parentObject, $propertyName);
483 if (!empty($currentFieldValue)) {
484 $currentUids = t3lib_div::intExplode(',', $currentFieldValue);
485 }
486 }
487
488 $removedObjectUids = array();
489 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
490 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
491 $this->removeObject($removedObject);
492 } else {
493 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
494 $removedObjectUids[] = $removedObject->getUid();
495 }
496 }
497
498 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
499 $this->deleteAllRelationsFromRelationtable($parentObject, $propertyName);
500 }
501
502 $insertedObjectUids = array();
503 $sortingPosition = 1;
504 foreach ($objectStorage as $object) {
505 if ($object->_isNew()) {
506 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
507 $this->insertObject($object);
508 } else {
509 $this->persistValueObject($object, $sortingPosition);
510 }
511 $insertedObjectUids[] = $object->getUid();
512 }
513 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
514 $sortingPosition++;
515 }
516
517 if ($columnMap->getParentKeyFieldName() === NULL) {
518 $newUids = array_diff($currentUids, $removedObjectUids);
519 $newUids = array_merge($newUids, $insertedObjectUids);
520 $row[$columnMap->getColumnName()] = implode(',', $newUids);
521 } else {
522 $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
523 }
524 }
525
526 /**
527 * Returns the current field value of the given object property from the storage backend.
528 *
529 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
530 * @param string $propertyName The property name
531 * @return mixed The field value
532 */
533 protected function getCurrentFieldValue(Tx_Extbase_DomainObject_DomainObjectInterface $object, $propertyName) {
534 $className = get_class($object);
535 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
536 $query = $this->queryFactory->create($className);
537 $query->getQuerySettings()->setReturnRawQueryResult(TRUE);
538 $result = $query->matching($query->withUid($object->getUid()))->execute();
539 $rows = $result->getRows();
540 $currentRow = current(current($rows));
541 $fieldValue = $currentRow->getValue($columnMap->getColumnName());
542 return $fieldValue;
543 }
544
545 /**
546 * Returns the removed objects determined by a comparison of the clean property value
547 * with the actual property value.
548 *
549 * @param Tx_Extbase_DomainObject_AbstractEntity $object The object
550 * @param string $parentPropertyName The name of the property
551 * @return array An array of removed objects
552 */
553 protected function getRemovedChildObjects(Tx_Extbase_DomainObject_AbstractEntity $object, $propertyName) {
554 $removedObjects = array();
555 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
556 if (is_array($cleanPropertyValue) || $cleanPropertyValue instanceof Iterator) {
557 $propertyValue = $object->_getProperty($propertyName);
558 foreach ($cleanPropertyValue as $containedObject) {
559 if (!$propertyValue->contains($containedObject)) {
560 $removedObjects[] = $containedObject;
561 }
562 }
563 }
564 return $removedObjects;
565 }
566
567 /**
568 * Updates the fields defining the relation between the object and the parent object.
569 *
570 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
571 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
572 * @param string $parentPropertyName
573 * @return void
574 */
575 protected function attachObjectToParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0) {
576 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
577 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
578 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
579 $row = array();
580 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
581 if ($parentKeyFieldName !== NULL) {
582 $row[$parentKeyFieldName] = $parentObject->getUid();
583 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
584 if ($parentTableFieldName !== NULL) {
585 $row[$parentTableFieldName] = $parentDataMap->getTableName();
586 }
587 }
588 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
589 if (!empty($childSortByFieldName)) {
590 $row[$childSortByFieldName] = $sortingPosition;
591 }
592 if (count($row) > 0) {
593 $this->updateObject($object, $row);
594 }
595 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
596 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
597 }
598 }
599
600 /**
601 * Updates the fields defining the relation between the object and the parent object.
602 *
603 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
604 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
605 * @param string $parentPropertyName
606 * @return void
607 */
608 protected function detachObjectFromParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName) {
609 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
610 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
611 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
612 $row = array();
613 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
614 if ($parentKeyFieldName !== NULL) {
615 $row[$parentKeyFieldName] = '';
616 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
617 if ($parentTableFieldName !== NULL) {
618 $row[$parentTableFieldName] = '';
619 }
620 }
621 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
622 if (!empty($childSortByFieldName)) {
623 $row[$childSortByFieldName] = 0;
624 }
625 if (count($row) > 0) {
626 $this->updateObject($object, $row);
627 }
628 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
629 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
630 }
631 }
632
633 /**
634 * Inserts an object in the storage
635 *
636 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
637 * @param array $row The tuple to be inserted
638 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The parent object (if any)
639 * @param string $parentPropertyName The name of the property
640 */
641 protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $row = array()) {
642 $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
643 $this->addCommonFieldsToRow($object, $row);
644 $uid = $this->storageBackend->addRow(
645 $tableName,
646 $row
647 );
648 $object->_setProperty('uid', (int)$uid);
649 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
650 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
651 }
652 $this->identityMap->registerObject($object, $uid);
653 }
654
655 /**
656 * Inserts mm-relation into a relation table
657 *
658 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related object
659 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
660 * @param string $propertyName The name of the parent object's property where the related objects are stored in
661 * @return void
662 */
663 protected function insertRelationInRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $sortingPosition = NULL) {
664 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
665 $columnMap = $dataMap->getColumnMap($propertyName);
666 $row = array(
667 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
668 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
669 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
670 );
671 $relationTableName = $columnMap->getRelationTableName();
672 // FIXME Reenable support for tablenames
673 // $childTableName = $columnMap->getChildTableName();
674 // if (isset($childTableName)) {
675 // $row['tablenames'] = $childTableName;
676 // }
677 $res = $this->storageBackend->addRow(
678 $relationTableName,
679 $row,
680 TRUE);
681 return $res;
682 }
683
684 /**
685 * Delete all mm-relations of a parent from a relation table
686 *
687 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
688 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
689 * @return void
690 */
691 protected function deleteAllRelationsFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
692 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
693 $columnMap = $dataMap->getColumnMap($parentPropertyName);
694 $relationTableName = $columnMap->getRelationTableName();
695 $res = $this->storageBackend->removeRow(
696 $relationTableName,
697 array(
698 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
699 ),
700 FALSE);
701 return $res;
702 }
703
704 /**
705 * Delete an mm-relation from a relation table
706 *
707 * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
708 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
709 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
710 * @return void
711 */
712 protected function deleteRelationFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
713 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
714 $columnMap = $dataMap->getColumnMap($parentPropertyName);
715 $relationTableName = $columnMap->getRelationTableName();
716 $res = $this->storageBackend->removeRow(
717 $relationTableName,
718 array(
719 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
720 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(),
721 ),
722 FALSE);
723 return $res;
724 }
725
726 /**
727 * Updates a given object in the storage
728 *
729 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
730 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
731 * @param string|NULL $parentPropertyName The name of the property
732 * @param array $row The $row
733 */
734 protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
735 $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
736 $this->addCommonFieldsToRow($object, $row);
737 $uid = $object->getUid();
738 $row['uid'] = $uid;
739 $res = $this->storageBackend->updateRow(
740 $tableName,
741 $row
742 );
743 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
744 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
745 }
746 return $res;
747 }
748
749 /**
750 * Returns a table row to be inserted or updated in the database
751 *
752 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The appropriate data map representing a database table
753 * @param array $properties The properties of the object
754 * @return array A single row to be inserted in the database
755 */
756 protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
757 $className = get_class($object);
758 $dataMap = $this->dataMapper->getDataMap($className);
759 if ($dataMap->hasCreationDateColumn() && $object->_isNew()) {
760 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
761 }
762 if ($dataMap->hasTimestampColumn()) {
763 $row[$dataMap->getTimestampColumnName()] = $GLOBALS['EXEC_TIME'];
764 }
765 if ($object->_isNew() && $dataMap->hasPidColumn() && !isset($row['pid'])) {
766 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
767 }
768 }
769
770 /**
771 * Iterate over deleted aggregate root objects and process them
772 *
773 * @return void
774 */
775 protected function processDeletedObjects() {
776 foreach ($this->deletedObjects as $object) {
777 $this->removeObject($object);
778 $this->identityMap->unregisterObject($object);
779 }
780 $this->deletedObjects = new Tx_Extbase_Persistence_ObjectStorage();
781 }
782
783 /**
784 * Deletes an object
785 *
786 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
787 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
788 * @param string|NULL $parentPropertyName The name of the property
789 * @param bool $markAsDeleted Shold we only mark the row as deleted instead of deleting (TRUE by default)?
790 * @return void
791 */
792 protected function removeObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $markAsDeleted = TRUE) {
793 $dataMap = $this->dataMapper->getDataMap(get_class($object));
794 $tableName = $dataMap->getTableName();
795 if (($markAsDeleted === TRUE) && $dataMap->hasDeletedColumn()) {
796 $deletedColumnName = $dataMap->getDeletedColumnName();
797 $res = $this->storageBackend->updateRow(
798 $tableName,
799 array(
800 'uid' => $object->getUid(),
801 $deletedColumnName => 1
802 )
803 );
804 } else {
805 $res = $this->storageBackend->removeRow(
806 $tableName,
807 array('uid' => $object->getUid())
808 );
809 }
810 $this->removeRelatedObjects($object);
811 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
812 $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
813 }
814 }
815
816 /**
817 * Remove related objects
818 *
819 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to scanned for related objects
820 * @return void
821 */
822 protected function removeRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
823 $className = get_class($object);
824 $dataMap = $this->dataMapper->getDataMap($className);
825 $classSchema = $this->reflectionService->getClassSchema($className);
826
827 $properties = $object->_getProperties();
828 foreach ($properties as $propertyName => $propertyValue) {
829 $columnMap = $dataMap->getColumnMap($propertyName);
830 $propertyMetaData = $classSchema->getProperty($propertyName);
831 if ($propertyMetaData['cascade'] === 'remove') {
832 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
833 foreach ($propertyValue as $containedObject) {
834 $this->removeObject($containedObject);
835 }
836 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
837 $this->removeObject($propertyValue);
838 }
839 }
840 }
841 }
842
843 /**
844 * Delegates the call to the Data Map.
845 * Returns TRUE if the property is persistable (configured in $TCA)
846 *
847 * @param string $className The property name
848 * @param string $propertyName The property name
849 * @return boolean TRUE if the property is persistable (configured in $TCA)
850 */
851 public function isPersistableProperty($className, $propertyName) {
852 $dataMap = $this->dataMapper->getDataMap($className);
853 return $dataMap->isPersistableProperty($propertyName);
854 }
855
856 /**
857 * Determine the storage page ID for a given NEW record
858 *
859 * This does the following:
860 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
861 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
862 *
863 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
864 * @return int the storage Page ID where the object should be stored
865 */
866 protected function determineStoragePageIdForNewRecord(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
867 $className = get_class($object);
868 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
869
870 if (isset($extbaseSettings['persistence']['classes'][$className]) && !empty($extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'])) {
871 return (int)$extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'];
872 } else {
873 $storagePidList = t3lib_div::intExplode(',', $extbaseSettings['persistence']['storagePid']);
874 return (int) $storagePidList[0];
875 }
876 }
877
878 }
879
880 ?>