9fc69cd4cf0da5e195895c1f96a42cb8852c6222
[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 array
92 **/
93 protected $extbaseSettings;
94
95 /**
96 * Constructs the backend
97 *
98 * @return void
99 */
100 public function __construct() {
101 $this->extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
102 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
103 $this->referenceIndex = t3lib_div::makeInstance('t3lib_refindex');
104 }
105 }
106
107 /**
108 * @param Tx_Extbase_Persistence_Session $session
109 * @return void
110 */
111 public function injectSession(Tx_Extbase_Persistence_Session $session) {
112 $this->session = $session;
113 }
114
115 /**
116 * @param Tx_Extbase_Persistence_Storage_BackendInterface $storageBackend
117 * @return void
118 */
119 public function injectStorageBackend(Tx_Extbase_Persistence_Storage_BackendInterface $storageBackend) {
120 $this->storageBackend = $storageBackend;
121 }
122
123 /**
124 * Injects the DataMapper to map nodes to objects
125 *
126 * @param Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper
127 * @return void
128 */
129 public function injectDataMapper(Tx_Extbase_Persistence_Mapper_DataMapper $dataMapper) {
130 $this->dataMapper = $dataMapper;
131 }
132
133 /**
134 * Injects the identity map
135 *
136 * @param Tx_Extbase_Persistence_IdentityMap $identityMap
137 * @return void
138 */
139 public function injectIdentityMap(Tx_Extbase_Persistence_IdentityMap $identityMap) {
140 $this->identityMap = $identityMap;
141 }
142
143 /**
144 * Injects the Reflection Service
145 *
146 * @param Tx_Extbase_Reflection_Service
147 * @return void
148 */
149 public function injectReflectionService(Tx_Extbase_Reflection_Service $reflectionService) {
150 $this->reflectionService = $reflectionService;
151 }
152
153 /**
154 * Injects the QueryFactory
155 *
156 * @param Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory
157 * @return void
158 */
159 public function injectQueryFactory(Tx_Extbase_Persistence_QueryFactoryInterface $queryFactory) {
160 $this->queryFactory = $queryFactory;
161 }
162
163 /**
164 * Injects the QueryObjectModelFactory
165 *
166 * @param Tx_Extbase_Persistence_QOM_QueryObjectModelFactory $qomFactory
167 * @return void
168 */
169 public function injectQomFactory(Tx_Extbase_Persistence_QOM_QueryObjectModelFactory $qomFactory) {
170 $this->qomFactory = $qomFactory;
171 }
172
173 /**
174 * Returns the repository session
175 *
176 * @return Tx_Extbase_Persistence_Session
177 */
178 public function getSession() {
179 return $this->session;
180 }
181
182 /**
183 * Returns the Data Mapper
184 *
185 * @return Tx_Extbase_Persistence_Mapper_DataMapper
186 */
187 public function getDataMapper() {
188 return $this->dataMapper;
189 }
190
191 /**
192 * Returns the current QOM factory
193 *
194 * @return Tx_Extbase_Persistence_QOM_QueryObjectModelFactory
195 */
196 public function getQomFactory() {
197 return $this->qomFactory;
198 }
199
200 /**
201 * Returns the current identityMap
202 *
203 * @return Tx_Extbase_Persistence_IdentityMap
204 */
205 public function getIdentityMap() {
206 return $this->identityMap;
207 }
208
209 /**
210 * Returns the reflection service
211 *
212 * @return Tx_Extbase_Reflection_Service
213 */
214 public function getReflectionService() {
215 return $this->reflectionService;
216 }
217
218 /**
219 * Returns the number of records matching the query.
220 *
221 * @param Tx_Extbase_Persistence_QueryInterface $query
222 * @return integer
223 * @api
224 */
225 public function getObjectCountByQuery(Tx_Extbase_Persistence_QueryInterface $query) {
226 return $this->storageBackend->getObjectCountByQuery($query);
227 }
228
229 /**
230 * Returns the object data matching the $query.
231 *
232 * @param Tx_Extbase_Persistence_QueryInterface $query
233 * @return array
234 * @api
235 */
236 public function getObjectDataByQuery(Tx_Extbase_Persistence_QueryInterface $query) {
237 return $this->storageBackend->getObjectDataByQuery($query);
238 }
239
240 /**
241 * Returns the (internal) identifier for the object, if it is known to the
242 * backend. Otherwise NULL is returned.
243 *
244 * @param object $object
245 * @return string The identifier for the object if it is known, or NULL
246 */
247 public function getIdentifierByObject($object) {
248 if ($this->identityMap->hasObject($object)) {
249 return $this->identityMap->getIdentifierByObject($object);
250 } else {
251 return NULL;
252 }
253 }
254
255 /**
256 * Returns the object with the (internal) identifier, if it is known to the
257 * backend. Otherwise NULL is returned.
258 *
259 * @param string $identifier
260 * @param string $className
261 * @return object The object for the identifier if it is known, or NULL
262 */
263 public function getObjectByIdentifier($identifier, $className) {
264 if ($this->identityMap->hasIdentifier($identifier, $className)) {
265 return $this->identityMap->getObjectByIdentifier($identifier, $className);
266 } else {
267 $query = $this->queryFactory->create($className);
268 return $query->matching(
269 $query->withUid($identifier))
270 ->execute()
271 ->getFirst();
272 }
273 }
274
275 /**
276 * Checks if the given object has ever been persisted.
277 *
278 * @param object $object The object to check
279 * @return boolean TRUE if the object is new, FALSE if the object exists in the repository
280 */
281 public function isNewObject($object) {
282 return ($this->getIdentifierByObject($object) === NULL);
283 }
284
285 /**
286 * Replaces the given object by the second object.
287 *
288 * This method will unregister the existing object at the identity map and
289 * register the new object instead. The existing object must therefore
290 * already be registered at the identity map which is the case for all
291 * reconstituted objects.
292 *
293 * The new object will be identified by the uid which formerly belonged
294 * to the existing object. The existing object looses its uid.
295 *
296 * @param object $existingObject The existing object
297 * @param object $newObject The new object
298 * @return void
299 */
300 public function replaceObject($existingObject, $newObject) {
301 $existingUid = $this->getIdentifierByObject($existingObject);
302 if ($existingUid === NULL) throw new Tx_Extbase_Persistence_Exception_UnknownObject('The given object is unknown to this persistence backend.', 1238070163);
303
304 $this->identityMap->unregisterObject($existingObject);
305 $this->identityMap->registerObject($newObject, $existingUid);
306 }
307
308 /**
309 * Sets the aggregate root objects
310 *
311 * @param Tx_Extbase_Persistence_ObjectStorage $objects
312 * @return void
313 */
314 public function setAggregateRootObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
315 $this->aggregateRootObjects = $objects;
316 }
317
318 /**
319 * Sets the deleted objects
320 *
321 * @param Tx_Extbase_Persistence_ObjectStorage $objects
322 * @return void
323 */
324 public function setDeletedObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
325 $this->deletedObjects = $objects;
326 }
327
328 /**
329 * Commits the current persistence session.
330 *
331 * @return void
332 */
333 public function commit() {
334 $this->persistObjects();
335 $this->processDeletedObjects();
336 }
337
338 /**
339 * Traverse and persist all aggregate roots and their object graph.
340 *
341 * @return void
342 */
343 protected function persistObjects() {
344 $this->visitedDuringPersistence = new Tx_Extbase_Persistence_ObjectStorage();
345 foreach ($this->aggregateRootObjects as $object) {
346 if (!$this->identityMap->hasObject($object)) {
347 $this->insertObject($object);
348 }
349 }
350 foreach ($this->aggregateRootObjects as $object) {
351 $this->persistObject($object);
352 }
353 }
354
355 /**
356 * Persists an object (instert, update) and its related objects (instert, update, delete).
357 *
358 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be inserted
359 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
360 * @param string $parentPropertyName The name of the property the object is stored in
361 * @return void
362 */
363 protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
364 if (isset($this->visitedDuringPersistence[$object])) {
365 return $this->visitedDuringPersistence[$object];
366 } else {
367 $this->visitedDuringPersistence[$object] = $object->getUid();
368 }
369
370 $row = array();
371 $queue = array();
372 $className = get_class($object);
373 $dataMap = $this->dataMapper->getDataMap($className);
374 $classSchema = $this->reflectionService->getClassSchema($className);
375
376 $properties = $object->_getProperties();
377 foreach ($properties as $propertyName => $propertyValue) {
378 if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) continue;
379
380 $columnMap = $dataMap->getColumnMap($propertyName);
381 $propertyMetaData = $classSchema->getProperty($propertyName);
382 $propertyType = $propertyMetaData['type'];
383 // FIXME enable property-type check
384 // $this->checkPropertyType($propertyType, $propertyValue);
385 if (($propertyValue !== NULL) && ($propertyType === 'SplObjectStorage' || $propertyType === 'Tx_Extbase_Persistence_ObjectStorage')) {
386 if ($object->_isNew() || $object->_isDirty($propertyName)) {
387 $this->persistObjectStorage($propertyValue, $object, $propertyName, $queue, $row);
388 foreach ($propertyValue as $containedObject) {
389 if ($containedObject instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
390 $queue[] = $containedObject;
391 }
392 }
393 }
394 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
395 if ($object->_isDirty($propertyName)) {
396 if ($propertyValue->_isNew()) {
397 if ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractEntity) {
398 $this->insertObject($propertyValue);
399 } else {
400 $this->persistValueObject($propertyValue);
401 }
402 }
403 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
404 $queue[] = $propertyValue;
405 }
406 } elseif ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject || $object->_isNew() || $object->_isDirty($propertyName)) {
407 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
408 }
409 }
410
411 if (count($row) > 0) {
412 $this->updateObject($object, $row);
413 }
414
415 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
416 $object->_memorizeCleanState();
417 }
418
419 foreach ($queue as $queuedObject) {
420 $this->persistObject($queuedObject);
421 }
422
423 }
424
425 /**
426 * Checks a value given against the expected type. If not matching, an
427 * UnexpectedTypeException is thrown. NULL is always considered valid.
428 *
429 * @param string $expectedType The expected type
430 * @param mixed $value The value to check
431 * @return void
432 * @throws Tx_Extbase_Persistence_Exception_UnexpectedType
433 */
434 protected function checkPropertyType($expectedType, $value) {
435 if ($value === NULL) {
436 return;
437 }
438
439 if (is_object($value)) {
440 if (!($value instanceof $expectedType)) {
441 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . get_class($value), 1244465558);
442 }
443 } elseif ($expectedType !== gettype($value)) {
444 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . gettype($value), 1244465558);
445 }
446 }
447
448 /**
449 * Checks, if the property value is lazy loaded and was not initialized
450 *
451 * @param mixed $propertyValue The property value
452 * @return bool
453 */
454 public function propertyValueIsLazyLoaded($propertyValue) {
455 if ($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) return TRUE;
456 if (is_object($propertyValue) && get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') {
457 if ($propertyValue->isInitialized() === FALSE) {
458 return TRUE;
459 }
460 }
461 return FALSE;
462 }
463
464 /**
465 * Persists the given value object.
466 *
467 * @return void
468 */
469 protected function persistValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object, $sortingPosition = 1) {
470 $result = $this->getUidOfAlreadyPersistedValueObject($object);
471 if ($result !== FALSE) {
472 $object->_setProperty('uid', (int)$result);
473 } elseif ($object->_isNew()) {
474 $row = array();
475 $className = get_class($object);
476 $dataMap = $this->dataMapper->getDataMap($className);
477 $classSchema = $this->reflectionService->getClassSchema($className);
478
479 $properties = $object->_getProperties();
480 foreach ($properties as $propertyName => $propertyValue) {
481 if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) continue;
482
483 $columnMap = $dataMap->getColumnMap($propertyName);
484 $propertyMetaData = $classSchema->getProperty($propertyName);
485 $propertyType = $propertyMetaData['type'];
486 // FIXME enable property-type check
487 // $this->checkPropertyType($propertyType, $propertyValue);
488 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
489 }
490 $this->insertObject($object, $row);
491 }
492 }
493
494 /**
495 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
496 *
497 * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
498 */
499 protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
500 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
501 }
502
503 /**
504 * 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
505 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
506 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
507 *
508 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
509 * @param string $propertyName The name of the property the related objects are stored in
510 * @param mixed $propertyValue The property value
511 * @return void
512 */
513 protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, array &$queue, array &$row) {
514 $className = get_class($parentObject);
515 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
516 $columnName = $columnMap->getColumnName();
517 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
518
519 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
520 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
521 $this->removeObject($removedObject);
522 } else {
523 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
524 }
525 }
526
527 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
528 $this->deleteAllRelationsFromRelationtable($parentObject, $propertyName);
529 }
530
531 $currentUids = array();
532 $sortingPosition = 1;
533 foreach ($objectStorage as $object) {
534 if ($object->_isNew()) {
535 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
536 $this->insertObject($object);
537 } else {
538 $this->persistValueObject($object, $sortingPosition);
539 }
540 }
541 $currentUids[] = $object->getUid();
542 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
543 $sortingPosition++;
544 }
545
546 if ($columnMap->getParentKeyFieldName() === NULL) {
547 $row[$columnMap->getColumnName()] = implode(',', $currentUids);
548 } else {
549 $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
550 }
551 }
552
553 /**
554 * Returns the current field value of the given object property from the storage backend.
555 *
556 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
557 * @param string $propertyName The property name
558 * @return mixed The field value
559 */
560 protected function getCurrentFieldValue(Tx_Extbase_DomainObject_DomainObjectInterface $object, $propertyName) {
561 $className = get_class($object);
562 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
563 $query = $this->queryFactory->create($className);
564 $query->getQuerySettings()->setReturnRawQueryResult(TRUE);
565 $currentRow = $query->matching(
566 $query->withUid($object->getUid()))
567 ->execute()
568 ->getFirst();
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 ?>