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