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