Raising Extbase and Fluid to version 1.2.0
[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: Backend.php 2292 2010-05-25 11:11:04Z jocrau $
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) || $this->propertyValueIsLazyLoaded($propertyValue)) continue;
481
482 $columnMap = $dataMap->getColumnMap($propertyName);
483 $propertyMetaData = $classSchema->getProperty($propertyName);
484 $propertyType = $propertyMetaData['type'];
485 // FIXME enable property-type check
486 // $this->checkPropertyType($propertyType, $propertyValue);
487 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
488 }
489 $this->insertObject($object, $row);
490 }
491 }
492
493 /**
494 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
495 *
496 * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
497 */
498 protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
499 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
500 }
501
502 /**
503 * 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
504 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
505 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
506 *
507 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
508 * @param string $propertyName The name of the property the related objects are stored in
509 * @param mixed $propertyValue The property value
510 * @return void
511 */
512 protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, array &$queue, array &$row) {
513 $className = get_class($parentObject);
514 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
515 $columnName = $columnMap->getColumnName();
516 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
517
518 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
519 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
520 $this->removeObject($removedObject);
521 } else {
522 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
523 }
524 }
525
526 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
527 $this->deleteAllRelationsFromRelationtable($parentObject, $propertyName);
528 }
529
530 $currentUids = array();
531 $sortingPosition = 1;
532 foreach ($objectStorage as $object) {
533 if ($object->_isNew()) {
534 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
535 $this->insertObject($object);
536 } else {
537 $this->persistValueObject($object, $sortingPosition);
538 }
539 }
540 $currentUids[] = $object->getUid();
541 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
542 $sortingPosition++;
543 }
544
545 if ($columnMap->getParentKeyFieldName() === NULL) {
546 $row[$columnMap->getColumnName()] = implode(',', $currentUids);
547 } else {
548 $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
549 }
550 }
551
552 /**
553 * Returns the current field value of the given object property from the storage backend.
554 *
555 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
556 * @param string $propertyName The property name
557 * @return mixed The field value
558 */
559 protected function getCurrentFieldValue(Tx_Extbase_DomainObject_DomainObjectInterface $object, $propertyName) {
560 $className = get_class($object);
561 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
562 $query = $this->queryFactory->create($className);
563 $query->getQuerySettings()->setReturnRawQueryResult(TRUE);
564 $rows = $query->matching($query->withUid($object->getUid()))->execute();
565 $currentRow = current($rows);
566 $fieldValue = $currentRow[$columnMap->getColumnName()];
567 return $fieldValue;
568 }
569
570 /**
571 * Returns the removed objects determined by a comparison of the clean property value
572 * with the actual property value.
573 *
574 * @param Tx_Extbase_DomainObject_AbstractEntity $object The object
575 * @param string $parentPropertyName The name of the property
576 * @return array An array of removed objects
577 */
578 protected function getRemovedChildObjects(Tx_Extbase_DomainObject_AbstractEntity $object, $propertyName) {
579 $removedObjects = array();
580 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
581 if (is_array($cleanPropertyValue) || $cleanPropertyValue instanceof Iterator) {
582 $propertyValue = $object->_getProperty($propertyName);
583 foreach ($cleanPropertyValue as $containedObject) {
584 if (!$propertyValue->contains($containedObject)) {
585 $removedObjects[] = $containedObject;
586 }
587 }
588 }
589 return $removedObjects;
590 }
591
592 /**
593 * Updates the fields defining the relation between the object and the parent object.
594 *
595 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
596 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
597 * @param string $parentPropertyName
598 * @return void
599 */
600 protected function attachObjectToParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0) {
601 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
602 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
603 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
604 $row = array();
605 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
606 if ($parentKeyFieldName !== NULL) {
607 $row[$parentKeyFieldName] = $parentObject->getUid();
608 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
609 if ($parentTableFieldName !== NULL) {
610 $row[$parentTableFieldName] = $parentDataMap->getTableName();
611 }
612 }
613 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
614 if (!empty($childSortByFieldName)) {
615 $row[$childSortByFieldName] = $sortingPosition;
616 }
617 if (count($row) > 0) {
618 $this->updateObject($object, $row);
619 }
620 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
621 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
622 }
623 }
624
625 /**
626 * Updates the fields defining the relation between the object and the parent object.
627 *
628 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
629 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
630 * @param string $parentPropertyName
631 * @return void
632 */
633 protected function detachObjectFromParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName) {
634 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
635 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
636 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
637 $row = array();
638 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
639 if ($parentKeyFieldName !== NULL) {
640 $row[$parentKeyFieldName] = '';
641 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
642 if ($parentTableFieldName !== NULL) {
643 $row[$parentTableFieldName] = '';
644 }
645 }
646 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
647 if (!empty($childSortByFieldName)) {
648 $row[$childSortByFieldName] = 0;
649 }
650 if (count($row) > 0) {
651 $this->updateObject($object, $row);
652 }
653 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
654 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
655 }
656 }
657
658 /**
659 * Inserts an object in the storage backend
660 *
661 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
662 * @return void
663 */
664 protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $row = array()) {
665 $dataMap = $this->dataMapper->getDataMap(get_class($object));
666 $this->addCommonFieldsToRow($object, $row);
667 if($dataMap->getLanguageIdColumnName() !== NULL) {
668 $row[$dataMap->getLanguageIdColumnName()] = -1;
669 }
670 $uid = $this->storageBackend->addRow(
671 $dataMap->getTableName(),
672 $row
673 );
674 $object->_setProperty('uid', (int)$uid);
675 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
676 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $uid);
677 }
678 $this->identityMap->registerObject($object, $uid);
679 }
680
681 /**
682 * Inserts mm-relation into a relation table
683 *
684 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related object
685 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
686 * @param string $propertyName The name of the parent object's property where the related objects are stored in
687 * @return void
688 */
689 protected function insertRelationInRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $sortingPosition = NULL) {
690 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
691 $columnMap = $dataMap->getColumnMap($propertyName);
692 $row = array(
693 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
694 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
695 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
696 );
697 $relationTableName = $columnMap->getRelationTableName();
698 // FIXME Reenable support for tablenames
699 // $childTableName = $columnMap->getChildTableName();
700 // if (isset($childTableName)) {
701 // $row['tablenames'] = $childTableName;
702 // }
703 if ($columnMap->getRelationTablePageIdColumnName() !== NULL) {
704 $row[$columnMap->getRelationTablePageIdColumnName()] = $this->determineStoragePageIdForNewRecord();
705 }
706
707 $relationTableInsertFields = $columnMap->getRelationTableInsertFields();
708 if (count($relationTableInsertFields)) {
709 foreach($relationTableInsertFields as $insertField => $insertValue) {
710 $row[$insertField] = $insertValue;
711 }
712 }
713
714 $res = $this->storageBackend->addRow(
715 $relationTableName,
716 $row,
717 TRUE);
718 return $res;
719 }
720
721 /**
722 * Delete all mm-relations of a parent from a relation table
723 *
724 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
725 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
726 * @return void
727 */
728 protected function deleteAllRelationsFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
729 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
730 $columnMap = $dataMap->getColumnMap($parentPropertyName);
731 $relationTableName = $columnMap->getRelationTableName();
732
733 $relationMatchFields = array(
734 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
735 );
736
737 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
738 if (is_array($relationTableMatchFields) && count($relationTableMatchFields) > 0) {
739 $relationMatchFields = array_merge($relationTableMatchFields,$relationMatchFields);
740 }
741
742 $res = $this->storageBackend->removeRow(
743 $relationTableName,
744 $relationMatchFields,
745 FALSE);
746 return $res;
747 }
748
749 /**
750 * Delete an mm-relation from a relation table
751 *
752 * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
753 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
754 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
755 * @return void
756 */
757 protected function deleteRelationFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
758 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
759 $columnMap = $dataMap->getColumnMap($parentPropertyName);
760 $relationTableName = $columnMap->getRelationTableName();
761 $res = $this->storageBackend->removeRow(
762 $relationTableName,
763 array(
764 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
765 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(),
766 ),
767 FALSE);
768 return $res;
769 }
770
771 /**
772 * Updates a given object in the storage
773 *
774 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
775 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
776 * @param string|NULL $parentPropertyName The name of the property
777 * @param array $row The $row
778 */
779 protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array $row) {
780 $dataMap = $this->dataMapper->getDataMap(get_class($object));
781 $this->addCommonFieldsToRow($object, $row);
782 $row['uid'] = $object->getUid();
783 if($dataMap->getLanguageIdColumnName() !== NULL) {
784 $row[$dataMap->getLanguageIdColumnName()] = $object->_getProperty('_languageUid');
785 if ($object->_getProperty('_localizedUid') !== NULL) {
786 $row['uid'] = $object->_getProperty('_localizedUid');
787 }
788 }
789 $res = $this->storageBackend->updateRow(
790 $dataMap->getTableName(),
791 $row
792 );
793 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
794 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $row['uid']);
795 }
796 return $res;
797 }
798
799 /**
800 * Returns a table row to be inserted or updated in the database
801 *
802 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The appropriate data map representing a database table
803 * @param array $properties The properties of the object
804 * @return array A single row to be inserted in the database
805 */
806 protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
807 $className = get_class($object);
808 $dataMap = $this->dataMapper->getDataMap($className);
809 if ($object->_isNew() && ($dataMap->getCreationDateColumnName() !== NULL)) {
810 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
811 }
812 if ($dataMap->getModificationDateColumnName() !== NULL) {
813 $row[$dataMap->getModificationDateColumnName()] = $GLOBALS['EXEC_TIME'];
814 }
815 if ($dataMap->getRecordTypeColumnName() !== NULL && $dataMap->getRecordType() !== NULL) {
816 $row[$dataMap->getRecordTypeColumnName()] = $dataMap->getRecordType();
817 }
818 if ($object->_isNew() && !isset($row['pid'])) {
819 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
820 }
821 }
822
823 /**
824 * Iterate over deleted aggregate root objects and process them
825 *
826 * @return void
827 */
828 protected function processDeletedObjects() {
829 foreach ($this->deletedObjects as $object) {
830 $this->removeObject($object);
831 $this->identityMap->unregisterObject($object);
832 }
833 $this->deletedObjects = new Tx_Extbase_Persistence_ObjectStorage();
834 }
835
836 /**
837 * Deletes an object
838 *
839 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
840 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
841 * @param string|NULL $parentPropertyName The name of the property
842 * @param bool $markAsDeleted Shold we only mark the row as deleted instead of deleting (TRUE by default)?
843 * @return void
844 */
845 protected function removeObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $markAsDeleted = TRUE) {
846 $dataMap = $this->dataMapper->getDataMap(get_class($object));
847 $tableName = $dataMap->getTableName();
848 if (($markAsDeleted === TRUE) && ($dataMap->getDeletedFlagColumnName() !== NULL)) {
849 $deletedColumnName = $dataMap->getDeletedFlagColumnName();
850 $res = $this->storageBackend->updateRow(
851 $tableName,
852 array(
853 'uid' => $object->getUid(),
854 $deletedColumnName => 1
855 )
856 );
857 } else {
858 $res = $this->storageBackend->removeRow(
859 $tableName,
860 array('uid' => $object->getUid())
861 );
862 }
863 $this->removeRelatedObjects($object);
864 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
865 $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
866 }
867 }
868
869 /**
870 * Remove related objects
871 *
872 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to scanned for related objects
873 * @return void
874 */
875 protected function removeRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
876 $className = get_class($object);
877 $dataMap = $this->dataMapper->getDataMap($className);
878 $classSchema = $this->reflectionService->getClassSchema($className);
879
880 $properties = $object->_getProperties();
881 foreach ($properties as $propertyName => $propertyValue) {
882 $columnMap = $dataMap->getColumnMap($propertyName);
883 $propertyMetaData = $classSchema->getProperty($propertyName);
884 if ($propertyMetaData['cascade'] === 'remove') {
885 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
886 foreach ($propertyValue as $containedObject) {
887 $this->removeObject($containedObject);
888 }
889 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
890 $this->removeObject($propertyValue);
891 }
892 }
893 }
894 }
895
896 /**
897 * Determine the storage page ID for a given NEW record
898 *
899 * This does the following:
900 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
901 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
902 *
903 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
904 * @return int the storage Page ID where the object should be stored
905 */
906 protected function determineStoragePageIdForNewRecord(Tx_Extbase_DomainObject_DomainObjectInterface $object = NULL) {
907 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
908 if ($object !== NULL) {
909 $className = get_class($object);
910 if (isset($extbaseSettings['persistence']['classes'][$className]) && !empty($extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'])) {
911 return (int)$extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'];
912 }
913 }
914 $storagePidList = t3lib_div::intExplode(',', $extbaseSettings['persistence']['storagePid']);
915 return (int) $storagePidList[0];
916 }
917
918 /**
919 * Returns a plain value, i.e. objects are flattened out if possible.
920 *
921 * @param mixed $input
922 * @return mixed
923 */
924 protected function getPlainValue($input) {
925 if ($input instanceof DateTime) {
926 return $input->format('U');
927 } elseif ($input instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
928 return $input->getUid();
929 } elseif (is_bool($input)) {
930 return $input === TRUE ? 1 : 0;
931 } else {
932 return $input;
933 }
934 }
935
936 }
937
938 ?>