[+TASK] Extbase: Removed some obsolete comments.
[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_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 $qomFactory
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 reflection service
217 *
218 * @return Tx_Extbase_Reflection_Service
219 */
220 public function getReflectionService() {
221 return $this->reflectionService;
222 }
223
224 /**
225 * Returns the (internal) identifier for the object, if it is known to the
226 * backend. Otherwise NULL is returned.
227 *
228 * @param object $object
229 * @return string The identifier for the object if it is known, or NULL
230 */
231 public function getIdentifierByObject($object) {
232 if ($this->identityMap->hasObject($object)) {
233 return $this->identityMap->getIdentifierByObject($object);
234 } else {
235 return NULL;
236 }
237 }
238
239 /**
240 * Returns the object with the (internal) identifier, if it is known to the
241 * backend. Otherwise NULL is returned.
242 *
243 * @param string $identifier
244 * @param string $className
245 * @return object The object for the identifier if it is known, or NULL
246 */
247 public function getObjectByIdentifier($identifier, $className) {
248 if ($this->identityMap->hasIdentifier($identifier, $className)) {
249 return $this->identityMap->getObjectByIdentifier($identifier, $className);
250 } else {
251 $query = $this->queryFactory->create($className);
252 $result = $query->matching($query->withUid($identifier))->execute();
253 $object = NULL;
254 if (count($result) > 0) {
255 $object = current($result);
256 }
257 return $object;
258 }
259 }
260
261 /**
262 * Checks if the given object has ever been persisted.
263 *
264 * @param object $object The object to check
265 * @return boolean TRUE if the object is new, FALSE if the object exists in the repository
266 */
267 public function isNewObject($object) {
268 return ($this->getIdentifierByObject($object) === NULL);
269 }
270
271 /**
272 * Replaces the given object by the second object.
273 *
274 * This method will unregister the existing object at the identity map and
275 * register the new object instead. The existing object must therefore
276 * already be registered at the identity map which is the case for all
277 * reconstituted objects.
278 *
279 * The new object will be identified by the uid which formerly belonged
280 * to the existing object. The existing object looses its uid.
281 *
282 * @param object $existingObject The existing object
283 * @param object $newObject The new object
284 * @return void
285 */
286 public function replaceObject($existingObject, $newObject) {
287 $existingUid = $this->getIdentifierByObject($existingObject);
288 if ($existingUid === NULL) throw new Tx_Extbase_Persistence_Exception_UnknownObject('The given object is unknown to this persistence backend.', 1238070163);
289
290 $this->identityMap->unregisterObject($existingObject);
291 $this->identityMap->registerObject($newObject, $existingUid);
292 }
293
294 /**
295 * Sets the aggregate root objects
296 *
297 * @param Tx_Extbase_Persistence_ObjectStorage $objects
298 * @return void
299 */
300 public function setAggregateRootObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
301 $this->aggregateRootObjects = $objects;
302 }
303
304 /**
305 * Sets the deleted objects
306 *
307 * @param Tx_Extbase_Persistence_ObjectStorage $objects
308 * @return void
309 */
310 public function setDeletedObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
311 $this->deletedObjects = $objects;
312 }
313
314 /**
315 * Commits the current persistence session.
316 *
317 * @return void
318 */
319 public function commit() {
320 $this->persistObjects();
321 $this->processDeletedObjects();
322 }
323
324 /**
325 * Traverse and persist all aggregate roots and their object graph.
326 *
327 * @return void
328 */
329 protected function persistObjects() {
330 foreach ($this->aggregateRootObjects as $object) {
331 if (!$this->identityMap->hasObject($object)) {
332 $this->insertObject($object);
333 }
334 }
335 foreach ($this->aggregateRootObjects as $object) {
336 $this->persistObject($object);
337 }
338 }
339
340 /**
341 * Persists an object (instert, update) and its related objects (instert, update, delete).
342 *
343 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be inserted
344 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
345 * @param string $parentPropertyName The name of the property the object is stored in
346 * @return void
347 */
348 protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
349 $row = array();
350 $queue = array();
351 $className = get_class($object);
352 $dataMap = $this->dataMapper->getDataMap($className);
353 $classSchema = $this->reflectionService->getClassSchema($className);
354
355 $properties = $object->_getProperties();
356 foreach ($properties as $propertyName => $propertyValue) {
357 if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) continue;
358
359 $columnMap = $dataMap->getColumnMap($propertyName);
360 $childClassName = $columnMap->getChildClassName();
361 $propertyMetaData = $classSchema->getProperty($propertyName);
362 $propertyType = $propertyMetaData['type'];
363 // FIXME enable property-type check
364 // $this->checkPropertyType($propertyType, $propertyValue);
365 if (($propertyValue !== NULL) && ($propertyValue instanceof Tx_Extbase_Persistence_ObjectStorage || $propertyType === 'Tx_Extbase_Persistence_ObjectStorage')) {
366 if ($object->_isNew() || $object->_isDirty($propertyName)) {
367 $this->persistObjectStorage($propertyValue, $object, $propertyName, $queue, $row);
368 }
369 if (is_array($propertyValue)) {
370 foreach ($propertyValue as $containedObject) {
371 if ($containedObject instanceof Tx_Extbase_DomainObject_AbstractEntity) {
372 $queue[] = $containedObject;
373 }
374 }
375 }
376 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
377 if ($object->_isDirty($propertyName)) {
378 if ($propertyValue->_isNew()) {
379 if ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractEntity) {
380 $this->insertObject($propertyValue);
381 $queue[] = $propertyValue;
382 } else {
383 $this->persistValueObject($propertyValue);
384 }
385 }
386 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
387 }
388 } elseif ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject || $object->_isNew() || $object->_isDirty($propertyName)) {
389 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
390 }
391 }
392
393 if (count($row) > 0) {
394 $this->updateObject($object, $row);
395 }
396
397 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
398 $object->_memorizeCleanState();
399 }
400
401 foreach ($queue as $queuedObject) {
402 $this->persistObject($queuedObject);
403 }
404
405 }
406
407 /**
408 * Checks a value given against the expected type. If not matching, an
409 * UnexpectedTypeException is thrown. NULL is always considered valid.
410 *
411 * @param string $expectedType The expected type
412 * @param mixed $value The value to check
413 * @return void
414 * @throws Tx_Extbase_Persistence_Exception_UnexpectedType
415 */
416 protected function checkPropertyType($expectedType, $value) {
417 if ($value === NULL) {
418 return;
419 }
420
421 if (is_object($value)) {
422 if (!($value instanceof $expectedType)) {
423 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . get_class($value), 1244465558);
424 }
425 } elseif ($expectedType !== gettype($value)) {
426 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . gettype($value), 1244465558);
427 }
428 }
429
430 public function propertyValueIsLazyLoaded($propertyValue) {
431 if ($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) return TRUE;
432 if (is_object($propertyValue) && get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') {
433 if ($propertyValue->isInitialized() === FALSE) {
434 return TRUE;
435 }
436 }
437 return FALSE;
438 }
439
440 /**
441 * Persists the given value object.
442 *
443 * @return void
444 */
445 protected function persistValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object, $sortingPosition = 1) {
446 $result = $this->getUidOfAlreadyPersistedValueObject($object);
447 if ($result !== FALSE) {
448 $object->_setProperty('uid', (int)$result);
449 } elseif ($object->_isNew()) {
450 $row = array();
451 $className = get_class($object);
452 $dataMap = $this->dataMapper->getDataMap($className);
453 $classSchema = $this->reflectionService->getClassSchema($className);
454
455 $properties = $object->_getProperties();
456 foreach ($properties as $propertyName => $propertyValue) {
457 if (!$dataMap->isPersistableProperty($propertyName)) continue;
458 if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
459 continue;
460 }
461
462 $columnMap = $dataMap->getColumnMap($propertyName);
463 $propertyMetaData = $classSchema->getProperty($propertyName);
464 $propertyType = $propertyMetaData['type'];
465 // FIXME enable property-type check
466 // $this->checkPropertyType($propertyType, $propertyValue);
467 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
468 }
469 $this->insertObject($object, $row);
470 }
471 }
472
473 /**
474 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
475 *
476 * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
477 */
478 protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
479 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
480 }
481
482 /**
483 * 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
484 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
485 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
486 *
487 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
488 * @param string $propertyName The name of the property the related objects are stored in
489 * @param mixed $propertyValue The property value
490 * @return void
491 */
492 protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, array &$queue, array &$row) {
493 $className = get_class($parentObject);
494 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
495 $columnName = $columnMap->getColumnName();
496 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
497
498 $currentUids = array();
499 if ($columnMap->getParentKeyFieldName() === NULL) {
500 $currentFieldValue = $this->getCurrentFieldValue($parentObject, $propertyName);
501 if (!empty($currentFieldValue)) {
502 $currentUids = t3lib_div::intExplode(',', $currentFieldValue);
503 }
504 }
505
506 $removedObjectUids = array();
507 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
508 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
509 $this->removeObject($removedObject);
510 } else {
511 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
512 $removedObjectUids[] = $removedObject->getUid();
513 }
514 }
515
516 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
517 $this->deleteAllRelationsFromRelationtable($parentObject, $propertyName);
518 }
519
520 $insertedObjectUids = array();
521 $sortingPosition = 1;
522 foreach ($objectStorage as $object) {
523 if ($object->_isNew()) {
524 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
525 $this->insertObject($object);
526 } else {
527 $this->persistValueObject($object, $sortingPosition);
528 }
529 $insertedObjectUids[] = $object->getUid();
530 }
531 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
532 $sortingPosition++;
533 }
534
535 if ($columnMap->getParentKeyFieldName() === NULL) {
536 $newUids = array_diff($currentUids, $removedObjectUids);
537 $newUids = array_merge($newUids, $insertedObjectUids);
538 $row[$columnMap->getColumnName()] = implode(',', $newUids);
539 } else {
540 $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
541 }
542 }
543
544 /**
545 * Returns the current field value of the given object property from the storage backend.
546 *
547 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
548 * @param string $propertyName The property name
549 * @return mixed The field value
550 */
551 protected function getCurrentFieldValue(Tx_Extbase_DomainObject_DomainObjectInterface $object, $propertyName) {
552 $className = get_class($object);
553 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
554 $query = $this->queryFactory->create($className);
555 $query->getQuerySettings()->setReturnRawQueryResult(TRUE);
556 $result = $query->matching($query->withUid($object->getUid()))->execute();
557 $rows = $result->getRows();
558 $currentRow = current(current($rows));
559 $fieldValue = $currentRow->getValue($columnMap->getColumnName());
560 return $fieldValue;
561 }
562
563 /**
564 * Returns the removed objects determined by a comparison of the clean property value
565 * with the actual property value.
566 *
567 * @param Tx_Extbase_DomainObject_AbstractEntity $object The object
568 * @param string $parentPropertyName The name of the property
569 * @return array An array of removed objects
570 */
571 protected function getRemovedChildObjects(Tx_Extbase_DomainObject_AbstractEntity $object, $propertyName) {
572 $removedObjects = array();
573 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
574 if (is_array($cleanPropertyValue) || $cleanPropertyValue instanceof Iterator) {
575 $propertyValue = $object->_getProperty($propertyName);
576 foreach ($cleanPropertyValue as $containedObject) {
577 if (!$propertyValue->contains($containedObject)) {
578 $removedObjects[] = $containedObject;
579 }
580 }
581 }
582 return $removedObjects;
583 }
584
585 /**
586 * Updates the fields defining the relation between the object and the parent object.
587 *
588 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
589 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
590 * @param string $parentPropertyName
591 * @return void
592 */
593 protected function attachObjectToParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0) {
594 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
595 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
596 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
597 $row = array();
598 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
599 if ($parentKeyFieldName !== NULL) {
600 $row[$parentKeyFieldName] = $parentObject->getUid();
601 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
602 if ($parentTableFieldName !== NULL) {
603 $row[$parentTableFieldName] = $parentDataMap->getTableName();
604 }
605 }
606 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
607 if (!empty($childSortByFieldName)) {
608 $row[$childSortByFieldName] = $sortingPosition;
609 }
610 if (count($row) > 0) {
611 $this->updateObject($object, $row);
612 }
613 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
614 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
615 }
616 }
617
618 /**
619 * Updates the fields defining the relation between the object and the parent object.
620 *
621 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
622 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
623 * @param string $parentPropertyName
624 * @return void
625 */
626 protected function detachObjectFromParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName) {
627 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
628 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
629 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
630 $row = array();
631 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
632 if ($parentKeyFieldName !== NULL) {
633 $row[$parentKeyFieldName] = '';
634 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
635 if ($parentTableFieldName !== NULL) {
636 $row[$parentTableFieldName] = '';
637 }
638 }
639 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
640 if (!empty($childSortByFieldName)) {
641 $row[$childSortByFieldName] = 0;
642 }
643 if (count($row) > 0) {
644 $this->updateObject($object, $row);
645 }
646 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
647 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
648 }
649 }
650
651 /**
652 * Inserts an object in the storage
653 *
654 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
655 * @param array $row The tuple to be inserted
656 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The parent object (if any)
657 * @param string $parentPropertyName The name of the property
658 */
659 protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $row = array()) {
660 $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
661 $this->addCommonFieldsToRow($object, $row);
662 $uid = $this->storageBackend->addRow(
663 $tableName,
664 $row
665 );
666 $object->_setProperty('uid', (int)$uid);
667 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
668 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
669 }
670 $this->identityMap->registerObject($object, $uid);
671 }
672
673 /**
674 * Inserts mm-relation into a relation table
675 *
676 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related object
677 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
678 * @param string $propertyName The name of the parent object's property where the related objects are stored in
679 * @return void
680 */
681 protected function insertRelationInRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $sortingPosition = NULL) {
682 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
683 $columnMap = $dataMap->getColumnMap($propertyName);
684 $row = array(
685 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
686 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
687 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
688 );
689 $relationTableName = $columnMap->getRelationTableName();
690 // FIXME Reenable support for tablenames
691 // $childTableName = $columnMap->getChildTableName();
692 // if (isset($childTableName)) {
693 // $row['tablenames'] = $childTableName;
694 // }
695
696 $relationTableInsertFields = $columnMap->getRelationTableInsertFields();
697 if (count($relationTableInsertFields)) {
698 foreach($relationTableInsertFields as $insertField => $insertValue) {
699 $row[$insertField] = $insertValue;
700 }
701 }
702
703 $res = $this->storageBackend->addRow(
704 $relationTableName,
705 $row,
706 TRUE);
707 return $res;
708 }
709
710 /**
711 * Delete all mm-relations of a parent from a relation table
712 *
713 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
714 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
715 * @return void
716 */
717 protected function deleteAllRelationsFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
718 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
719 $columnMap = $dataMap->getColumnMap($parentPropertyName);
720 $relationTableName = $columnMap->getRelationTableName();
721
722 $relationMatchFields = array(
723 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
724 );
725
726 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
727 if (count($relationTableMatchFields)) {
728 $relationMatchFields = array_merge($relationTableMatchFields,$relationMatchFields);
729 }
730
731 $res = $this->storageBackend->removeRow(
732 $relationTableName,
733 $relationMatchFields,
734 FALSE);
735 return $res;
736 }
737
738 /**
739 * Delete an mm-relation from a relation table
740 *
741 * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
742 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
743 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
744 * @return void
745 */
746 protected function deleteRelationFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
747 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
748 $columnMap = $dataMap->getColumnMap($parentPropertyName);
749 $relationTableName = $columnMap->getRelationTableName();
750 $res = $this->storageBackend->removeRow(
751 $relationTableName,
752 array(
753 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
754 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(),
755 ),
756 FALSE);
757 return $res;
758 }
759
760 /**
761 * Updates a given object in the storage
762 *
763 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
764 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
765 * @param string|NULL $parentPropertyName The name of the property
766 * @param array $row The $row
767 */
768 protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
769 $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
770 $this->addCommonFieldsToRow($object, $row);
771 $uid = $object->getUid();
772 $row['uid'] = $uid;
773 $res = $this->storageBackend->updateRow(
774 $tableName,
775 $row
776 );
777 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
778 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
779 }
780 return $res;
781 }
782
783 /**
784 * Returns a table row to be inserted or updated in the database
785 *
786 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The appropriate data map representing a database table
787 * @param array $properties The properties of the object
788 * @return array A single row to be inserted in the database
789 */
790 protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
791 $className = get_class($object);
792 $dataMap = $this->dataMapper->getDataMap($className);
793 if ($dataMap->hasCreationDateColumn() && $object->_isNew()) {
794 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
795 }
796 if ($dataMap->hasTimestampColumn()) {
797 $row[$dataMap->getTimestampColumnName()] = $GLOBALS['EXEC_TIME'];
798 }
799 if ($object->_isNew() && $dataMap->hasPidColumn() && !isset($row['pid'])) {
800 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
801 }
802 }
803
804 /**
805 * Iterate over deleted aggregate root objects and process them
806 *
807 * @return void
808 */
809 protected function processDeletedObjects() {
810 foreach ($this->deletedObjects as $object) {
811 $this->removeObject($object);
812 $this->identityMap->unregisterObject($object);
813 }
814 $this->deletedObjects = new Tx_Extbase_Persistence_ObjectStorage();
815 }
816
817 /**
818 * Deletes an object
819 *
820 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
821 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
822 * @param string|NULL $parentPropertyName The name of the property
823 * @param bool $markAsDeleted Shold we only mark the row as deleted instead of deleting (TRUE by default)?
824 * @return void
825 */
826 protected function removeObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $markAsDeleted = TRUE) {
827 $dataMap = $this->dataMapper->getDataMap(get_class($object));
828 $tableName = $dataMap->getTableName();
829 if (($markAsDeleted === TRUE) && $dataMap->hasDeletedColumn()) {
830 $deletedColumnName = $dataMap->getDeletedColumnName();
831 $res = $this->storageBackend->updateRow(
832 $tableName,
833 array(
834 'uid' => $object->getUid(),
835 $deletedColumnName => 1
836 )
837 );
838 } else {
839 $res = $this->storageBackend->removeRow(
840 $tableName,
841 array('uid' => $object->getUid())
842 );
843 }
844 $this->removeRelatedObjects($object);
845 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
846 $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
847 }
848 }
849
850 /**
851 * Remove related objects
852 *
853 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to scanned for related objects
854 * @return void
855 */
856 protected function removeRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
857 $className = get_class($object);
858 $dataMap = $this->dataMapper->getDataMap($className);
859 $classSchema = $this->reflectionService->getClassSchema($className);
860
861 $properties = $object->_getProperties();
862 foreach ($properties as $propertyName => $propertyValue) {
863 $columnMap = $dataMap->getColumnMap($propertyName);
864 $propertyMetaData = $classSchema->getProperty($propertyName);
865 if ($propertyMetaData['cascade'] === 'remove') {
866 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
867 foreach ($propertyValue as $containedObject) {
868 $this->removeObject($containedObject);
869 }
870 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
871 $this->removeObject($propertyValue);
872 }
873 }
874 }
875 }
876
877 /**
878 * Delegates the call to the Data Map.
879 * Returns TRUE if the property is persistable (configured in $TCA)
880 *
881 * @param string $className The property name
882 * @param string $propertyName The property name
883 * @return boolean TRUE if the property is persistable (configured in $TCA)
884 */
885 public function isPersistableProperty($className, $propertyName) {
886 $dataMap = $this->dataMapper->getDataMap($className);
887 return $dataMap->isPersistableProperty($propertyName);
888 }
889
890 /**
891 * Determine the storage page ID for a given NEW record
892 *
893 * This does the following:
894 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
895 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
896 *
897 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
898 * @return int the storage Page ID where the object should be stored
899 */
900 protected function determineStoragePageIdForNewRecord(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
901 $className = get_class($object);
902 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
903
904 if (isset($extbaseSettings['persistence']['classes'][$className]) && !empty($extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'])) {
905 return (int)$extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'];
906 } else {
907 $storagePidList = t3lib_div::intExplode(',', $extbaseSettings['persistence']['storagePid']);
908 return (int) $storagePidList[0];
909 }
910 }
911
912 }
913
914 ?>