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