[+BUGFIX] Extbase (Persistence): Fixed a bug with a property set to NULL but was...
[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 2183 2009-04-24 14:28:37Z k-fish $
35 */
36 class Tx_Extbase_Persistence_Backend implements Tx_Extbase_Persistence_BackendInterface, t3lib_Singleton {
37
38 /**
39 * @var Tx_Extbase_Persistence_Session
40 */
41 protected $session;
42
43 /**
44 * @var Tx_Extbase_Persistence_ObjectStorage
45 */
46 protected $aggregateRootObjects;
47
48 /**
49 * @var Tx_Extbase_Persistence_IdentityMap
50 **/
51 protected $identityMap;
52
53 /**
54 * @var Tx_Extbase_Reflection_Service
55 */
56 protected $reflectionService;
57
58 /**
59 * @var Tx_Extbase_Persistence_QueryFactoryInterface
60 */
61 protected $queryFactory;
62
63 /**
64 * @var Tx_Extbase_Persistence_QOM_QueryObjectModelFactoryInterface
65 */
66 protected $QOMFactory;
67
68 /**
69 * @var Tx_Extbase_Persistence_ValueFactoryInterface
70 */
71 protected $valueFactory;
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 $dataMapper
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 value factory
199 *
200 * @return Tx_Extbase_Persistence_ValueFactoryInterface
201 */
202 public function getValueFactory() {
203 return $this->valueFactory;
204 }
205
206 /**
207 * Returns the current identityMap
208 *
209 * @return Tx_Extbase_Persistence_IdentityMap
210 */
211 public function getIdentityMap() {
212 return $this->identityMap;
213 }
214
215 /**
216 * Returns the (internal) identifier for the object, if it is known to the
217 * backend. Otherwise NULL is returned.
218 *
219 * @param object $object
220 * @return string The identifier for the object if it is known, or NULL
221 */
222 public function getIdentifierByObject($object) {
223 if ($this->identityMap->hasObject($object)) {
224 return $this->identityMap->getIdentifierByObject($object);
225 } else {
226 return NULL;
227 }
228 }
229
230 /**
231 * Returns the object with the (internal) identifier, if it is known to the
232 * backend. Otherwise NULL is returned.
233 *
234 * @param string $identifier
235 * @param string $className
236 * @return object The object for the identifier if it is known, or NULL
237 */
238 public function getObjectByIdentifier($identifier, $className) {
239 if ($this->identityMap->hasIdentifier($identifier, $className)) {
240 return $this->identityMap->getObjectByIdentifier($identifier, $className);
241 } else {
242 $query = $this->queryFactory->create($className);
243 $result = $query->matching($query->withUid($identifier))->execute();
244 $object = NULL;
245 if (count($result) > 0) {
246 $object = current($result);
247 }
248 return $object;
249 }
250 }
251
252 /**
253 * Checks if the given object has ever been persisted.
254 *
255 * @param object $object The object to check
256 * @return boolean TRUE if the object is new, FALSE if the object exists in the repository
257 */
258 public function isNewObject($object) {
259 return ($this->getIdentifierByObject($object) === NULL);
260 }
261
262 /**
263 * Replaces the given object by the second object.
264 *
265 * This method will unregister the existing object at the identity map and
266 * register the new object instead. The existing object must therefore
267 * already be registered at the identity map which is the case for all
268 * reconstituted objects.
269 *
270 * The new object will be identified by the uid which formerly belonged
271 * to the existing object. The existing object looses its uid.
272 *
273 * @param object $existingObject The existing object
274 * @param object $newObject The new object
275 * @return void
276 */
277 public function replaceObject($existingObject, $newObject) {
278 $existingUid = $this->getIdentifierByObject($existingObject);
279 if ($existingUid === NULL) throw new Tx_Extbase_Persistence_Exception_UnknownObject('The given object is unknown to this persistence backend.', 1238070163);
280
281 $this->identityMap->unregisterObject($existingObject);
282 $this->identityMap->registerObject($newObject, $existingUid);
283 }
284
285 /**
286 * Sets the aggregate root objects
287 *
288 * @param Tx_Extbase_Persistence_ObjectStorage $objects
289 * @return void
290 */
291 public function setAggregateRootObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
292 $this->aggregateRootObjects = $objects;
293 }
294
295 /**
296 * Sets the deleted objects
297 *
298 * @param Tx_Extbase_Persistence_ObjectStorage $objects
299 * @return void
300 */
301 public function setDeletedObjects(Tx_Extbase_Persistence_ObjectStorage $objects) {
302 $this->deletedObjects = $objects;
303 }
304
305 /**
306 * Commits the current persistence session.
307 *
308 * @return void
309 */
310 public function commit() {
311 $this->persistObjects();
312 $this->processDeletedObjects();
313 }
314
315 /**
316 * Traverse and persist all aggregate roots and their object graph.
317 *
318 * @return void
319 */
320 protected function persistObjects() {
321 foreach ($this->aggregateRootObjects as $object) {
322 // if (!$this->identityMap->hasObject($object)) { // TODO Must be enabled to allow other identity properties than $uid
323 if ($object->_isNew()) {
324 $this->insertObject($object);
325 }
326 }
327
328 foreach ($this->aggregateRootObjects as $object) {
329 $this->persistObject($object);
330 }
331 }
332
333 /**
334 * Persists an object (instert, update) and its related objects (instert, update, delete).
335 *
336 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be inserted
337 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
338 * @param string $parentPropertyName The name of the property the object is stored in
339 * @return void
340 */
341 protected function persistObject(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
342 $row = array();
343 $queue = array();
344 $className = get_class($object);
345 $dataMap = $this->dataMapper->getDataMap($className);
346 $classSchema = $this->reflectionService->getClassSchema($className);
347
348 $properties = $object->_getProperties();
349 foreach ($properties as $propertyName => $propertyValue) {
350 if (!$dataMap->isPersistableProperty($propertyName)) continue;
351 if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
352 continue;
353 }
354
355 $columnMap = $dataMap->getColumnMap($propertyName);
356 $propertyMetaData = $classSchema->getProperty($propertyName);
357 $propertyType = $propertyMetaData['type'];
358 // FIXME enable property-type check
359 // $this->checkPropertyType($propertyType, $propertyValue);
360 if (($propertyValue !== NULL) && ($propertyType === 'Tx_Extbase_Persistence_ObjectStorage')) {
361 if ($object->_isDirty($propertyName)) {
362 $row[$columnMap->getColumnName()] = $this->persistObjectStorage($propertyValue, $object, $propertyName, $queue);
363 } else {
364 foreach ($propertyValue as $containedObject) {
365 $queue[] = $containedObject;
366 }
367 }
368 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
369 if ($object->_isDirty($propertyName)) {
370 if ($propertyValue->_isNew()) {
371 if ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractEntity) {
372 $this->insertObject($propertyValue, $object, $propertyName);
373 $queue[] = $propertyValue;
374 } else {
375 $this->persistValueObject($propertyValue, $object, $propertyName);
376 }
377 }
378 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
379 }
380 } elseif ($object instanceof Tx_Extbase_DomainObject_AbstractValueObject || $object->_isNew() || $object->_isDirty($propertyName)) {
381 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
382 }
383 }
384
385 if (count($row) > 0) {
386 $this->updateObject($object, $row);
387 }
388
389 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
390 $object->_memorizeCleanState();
391 }
392
393 foreach ($queue as $object) {
394 $this->persistObject($object);
395 }
396
397 }
398
399 /**
400 * Checks a value given against the expected type. If not matching, an
401 * UnexpectedTypeException is thrown. NULL is always considered valid.
402 *
403 * @param string $expectedType The expected type
404 * @param mixed $value The value to check
405 * @return void
406 * @throws Tx_Extbase_Persistence_Exception_UnexpectedType
407 */
408 protected function checkPropertyType($expectedType, $value) {
409 if ($value === NULL) {
410 return;
411 }
412
413 if (is_object($value)) {
414 if (!($value instanceof $expectedType)) {
415 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . get_class($value), 1244465558);
416 }
417 } elseif ($expectedType !== gettype($value)) {
418 throw new Tx_Extbase_Persistence_Exception_UnexpectedTypeException('Expected property of type ' . $expectedType . ', but got ' . gettype($value), 1244465558);
419 }
420 }
421
422 /**
423 * Persists the given value object.
424 *
425 * @return void
426 */
427 protected function persistValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName, $sortingPosition = 1) {
428 $result = $this->getUidOfAlreadyPersistedValueObject($object);
429 if ($result !== FALSE) {
430 $object->_setProperty('uid', (int)$result);
431 if($this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($parentPropertyName)->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
432 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
433 }
434 } else {
435 $row = array();
436 $className = get_class($object);
437 $dataMap = $this->dataMapper->getDataMap($className);
438 $classSchema = $this->reflectionService->getClassSchema($className);
439
440 $properties = $object->_getProperties();
441 foreach ($properties as $propertyName => $propertyValue) {
442 if (!$dataMap->isPersistableProperty($propertyName)) continue;
443 if (($propertyValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) || ((get_class($propertyValue) === 'Tx_Extbase_Persistence_LazyObjectStorage') && ($propertyValue->isInitialized() === FALSE))) {
444 continue;
445 }
446
447 $columnMap = $dataMap->getColumnMap($propertyName);
448 $propertyMetaData = $classSchema->getProperty($propertyName);
449 $propertyType = $propertyMetaData['type'];
450 // FIXME enable property-type check
451 // $this->checkPropertyType($propertyType, $propertyValue);
452 $row[$columnMap->getColumnName()] = $dataMap->convertPropertyValueToFieldValue($propertyValue);
453 }
454 $this->insertObject($object, $parentObject, $parentPropertyName, $sortingPosition, $row);
455 }
456 }
457
458 /**
459 * Tests, if the given Value Object already exists in the storage backend. If so, it maps the uid
460 * to the given object.
461 *
462 * @param Tx_Extbase_DomainObject_AbstractValueObject $object The object to be tested
463 */
464 protected function getUidOfAlreadyPersistedValueObject(Tx_Extbase_DomainObject_AbstractValueObject $object) {
465 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
466 }
467
468 /**
469 * Persists a relation. Objects of a 1:n or m:n relation are queued and processed with the parent object. A 1:1 relation
470 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
471 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
472 *
473 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object
474 * @param string $propertyName The name of the property the related objects are stored in
475 * @param mixed $propertyValue The property value
476 * @return void
477 */
478 protected function persistObjectStorage(Tx_Extbase_Persistence_ObjectStorage $objectStorage, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, &$queue) {
479 $className = get_class($parentObject);
480 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
481 $columnName = $columnMap->getColumnName();
482 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
483
484 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
485 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
486 $this->removeObject($removedObject);
487 } else {
488 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
489 }
490 }
491
492 $childPidArray = array();
493 $sortingPosition = 1;
494 foreach ($objectStorage as $object) {
495 if ($object->_isNew()) {
496 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
497 $this->insertObject($object, $parentObject, $propertyName, $sortingPosition);
498 $queue[] = $object;
499 } else {
500 $this->persistValueObject($object, $parentObject, $propertyName, $sortingPosition);
501 }
502 }
503 $childPidArray[] = $object->getUid(); // FIXME This won't work for partly loaded storages
504 $sortingPosition++;
505 }
506
507 if ($columnMap->getParentKeyFieldName() === NULL) {
508 $newParentPropertyValue = implode(',', $childPidArray);
509 } else {
510 $newParentPropertyValue = count($objectStorage); // TODO check for limited queries
511 }
512
513 return $newParentPropertyValue;
514 }
515
516 /**
517 * Returns the removed objects determined by a comparison of the clean property value
518 * with the actual property value.
519 *
520 * @param Tx_Extbase_DomainObject_AbstractEntity $object The object
521 * @param string $parentPropertyName The name of the property
522 * @return array An array of removed objects
523 */
524 protected function getRemovedChildObjects(Tx_Extbase_DomainObject_AbstractEntity $object, $propertyName) {
525 $removedObjects = array();
526 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
527 $propertyValue = $object->_getProperty($propertyName);
528 if ($cleanPropertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
529 $cleanPropertyValue = $cleanPropertyValue->toArray();
530 }
531 if ($propertyValue instanceof Tx_Extbase_Persistence_ObjectStorage) {
532 $propertyValue = $propertyValue->toArray();
533 }
534 if ($cleanPropertyValue instanceof Iterator) {
535 foreach ($cleanPropertyValue as $hash => $item) {
536 if (!array_key_exists($hash, $propertyValue)) {
537 $removedObjects[] = $item;
538 }
539 }
540 }
541 return $removedObjects;
542 }
543
544 /**
545 * Updates the fields defining the relation between the object and the parent object.
546 *
547 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
548 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject
549 * @param string $parentPropertyName
550 * @return void
551 */
552 protected function detachObjectFromParentObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject, $parentPropertyName) {
553 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
554 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
555 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
556 $row = array();
557 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
558 if ($parentKeyFieldName !== NULL) {
559 $row[$parentKeyFieldName] = '';
560 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
561 if ($parentTableFieldName !== NULL) {
562 $row[$parentTableFieldName] = '';
563 }
564 }
565 if (count($row) > 0) {
566 $this->updateObject($object, $row);
567 }
568 } elseif ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
569 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
570 }
571 }
572
573 /**
574 * Inserts an object in the storage
575 *
576 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
577 * @param array $row The tuple to be inserted
578 * @param Tx_Extbase_DomainObject_AbstractEntity $parentObject The parent object (if any)
579 * @param string $parentPropertyName The name of the property
580 */
581 protected function insertObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_AbstractEntity $parentObject = NULL, $propertyName = NULL, $sortingPosition = NULL, array $row = array()) {
582 $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
583 $this->addCommonFieldsToRow($object, $row);
584 if ($parentObject !== NULL) {
585 $parentColumnMap = $this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($propertyName);
586 if ($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY && $parentColumnMap->getParentKeyFieldName() !== NULL) {
587 $row[$parentColumnMap->getParentKeyFieldName()] = $parentObject->getUid();
588 }
589 }
590 if ($object->_isNew()) {
591 $uid = $this->storageBackend->addRow(
592 $tableName,
593 $row
594 );
595 $object->_setProperty('uid', (int)$uid);
596 }
597 if ($parentObject !== NULL) {
598 if($parentColumnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
599 $this->insertRelationInRelationtable($object, $parentObject, $propertyName, $sortingPosition);
600 }
601 }
602 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
603 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
604 }
605 $this->identityMap->registerObject($object, $uid);
606 }
607
608 /**
609 * Inserts mm-relation into a relation table
610 *
611 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The related object
612 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
613 * @param string $propertyName The name of the parent object's property where the related objects are stored in
614 * @return void
615 */
616 protected function insertRelationInRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $object, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $propertyName, $sortingPosition = NULL) {
617 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
618 $columnMap = $dataMap->getColumnMap($propertyName);
619 $row = array(
620 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
621 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
622 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
623 );
624 $relationTableName = $columnMap->getRelationTableName();
625 // FIXME Reenable support for tablenames
626 // $childTableName = $columnMap->getChildTableName();
627 // if (isset($childTableName)) {
628 // $row['tablenames'] = $childTableName;
629 // }
630 $res = $this->storageBackend->addRow(
631 $relationTableName,
632 $row,
633 TRUE);
634 return $res;
635 }
636
637 /**
638 * Delete an mm-relation from a relation table
639 *
640 * @param Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject The related object
641 * @param Tx_Extbase_DomainObject_DomainObjectInterface $parentObject The parent object
642 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
643 * @return void
644 */
645 protected function deleteRelationFromRelationtable(Tx_Extbase_DomainObject_DomainObjectInterface $relatedObject, Tx_Extbase_DomainObject_DomainObjectInterface $parentObject, $parentPropertyName) {
646 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
647 $columnMap = $dataMap->getColumnMap($parentPropertyName);
648 $relationTableName = $columnMap->getRelationTableName();
649 $res = $this->storageBackend->removeRow(
650 $relationTableName,
651 array(
652 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
653 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid(),
654 ),
655 FALSE);
656 return $res;
657 }
658
659 /**
660 * Updates a given object in the storage
661 *
662 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
663 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
664 * @param string|NULL $parentPropertyName The name of the property
665 * @param array $row The $row
666 */
667 protected function updateObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
668 $tableName = $this->dataMapper->getDataMap(get_class($object))->getTableName();
669 $this->addCommonFieldsToRow($object, $row);
670 $uid = $object->getUid();
671 $row['uid'] = $uid;
672 $res = $this->storageBackend->updateRow(
673 $tableName,
674 $row
675 );
676 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
677 $this->referenceIndex->updateRefIndexTable($tableName, $uid);
678 }
679 if ($object instanceof Tx_Extbase_DomainObject_AbstractEntity) {
680 $object->_memorizeCleanState();
681 }
682 return $res;
683 }
684
685 /**
686 * Returns a table row to be inserted or updated in the database
687 *
688 * @param Tx_Extbase_Persistence_Mapper_DataMap $dataMap The appropriate data map representing a database table
689 * @param array $properties The properties of the object
690 * @return array A single row to be inserted in the database
691 */
692 protected function addCommonFieldsToRow(Tx_Extbase_DomainObject_DomainObjectInterface $object, array &$row) {
693 $className = get_class($object);
694 $dataMap = $this->dataMapper->getDataMap($className);
695 if ($dataMap->hasCreationDateColumn() && $object->_isNew()) {
696 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
697 }
698 if ($dataMap->hasTimestampColumn()) {
699 $row[$dataMap->getTimestampColumnName()] = $GLOBALS['EXEC_TIME'];
700 }
701 if ($object->_isNew() && $dataMap->hasPidColumn() && !isset($row['pid'])) {
702 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
703 }
704 }
705
706 /**
707 * Iterate over deleted aggregate root objects and process them
708 *
709 * @return void
710 */
711 protected function processDeletedObjects() {
712 foreach ($this->deletedObjects as $object) {
713 $this->removeObject($object);
714 $this->identityMap->unregisterObject($object);
715 }
716 $this->deletedObjects = new Tx_Extbase_Persistence_ObjectStorage();
717 }
718
719 /**
720 * Deletes an object
721 *
722 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to be insterted in the storage
723 * @param Tx_Extbase_DomainObject_AbstractEntity|NULL $parentObject The parent object (if any)
724 * @param string|NULL $parentPropertyName The name of the property
725 * @param bool $markAsDeleted Shold we only mark the row as deleted instead of deleting (TRUE by default)?
726 * @return void
727 */
728 protected function removeObject(Tx_Extbase_DomainObject_DomainObjectInterface $object, $markAsDeleted = TRUE) {
729 $dataMap = $this->dataMapper->getDataMap(get_class($object));
730 $tableName = $dataMap->getTableName();
731 if (($markAsDeleted === TRUE) && $dataMap->hasDeletedColumn()) {
732 $deletedColumnName = $dataMap->getDeletedColumnName();
733 $res = $this->storageBackend->updateRow(
734 $tableName,
735 array(
736 'uid' => $object->getUid(),
737 $deletedColumnName => 1
738 )
739 );
740 } else {
741 $res = $this->storageBackend->removeRow(
742 $tableName,
743 array('uid' => $object->getUid())
744 );
745 }
746 $this->removeRelatedObjects($object);
747 if ($this->extbaseSettings['persistence']['updateReferenceIndex'] === '1') {
748 $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
749 }
750 }
751
752 /**
753 * Remove related objects
754 *
755 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object The object to scanned for related objects
756 * @return void
757 */
758 protected function removeRelatedObjects(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
759 $className = get_class($object);
760 $dataMap = $this->dataMapper->getDataMap($className);
761 $classSchema = $this->reflectionService->getClassSchema($className);
762
763 $properties = $object->_getProperties();
764 foreach ($properties as $propertyName => $propertyValue) {
765 $columnMap = $dataMap->getColumnMap($propertyName);
766 $propertyMetaData = $classSchema->getProperty($propertyName);
767 if ($propertyMetaData['cascade'] === 'remove') {
768 if ($columnMap->getTypeOfRelation() === Tx_Extbase_Persistence_Mapper_ColumnMap::RELATION_HAS_MANY) {
769 foreach ($propertyValue as $containedObject) {
770 $this->removeObject($containedObject);
771 }
772 } elseif ($propertyValue instanceof Tx_Extbase_DomainObject_DomainObjectInterface) {
773 $this->removeObject($propertyValue);
774 }
775 }
776 }
777 }
778
779 /**
780 * Delegates the call to the Data Map.
781 * Returns TRUE if the property is persistable (configured in $TCA)
782 *
783 * @param string $className The property name
784 * @param string $propertyName The property name
785 * @return boolean TRUE if the property is persistable (configured in $TCA)
786 */
787 public function isPersistableProperty($className, $propertyName) {
788 $dataMap = $this->dataMapper->getDataMap($className);
789 return $dataMap->isPersistableProperty($propertyName);
790 }
791
792 /**
793 * Determine the storage page ID for a given NEW record
794 *
795 * This does the following:
796 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
797 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
798 *
799 * @param Tx_Extbase_DomainObject_DomainObjectInterface $object
800 * @return int the storage Page ID where the object should be stored
801 */
802 protected function determineStoragePageIdForNewRecord(Tx_Extbase_DomainObject_DomainObjectInterface $object) {
803 $className = get_class($object);
804 $extbaseSettings = Tx_Extbase_Dispatcher::getExtbaseFrameworkConfiguration();
805
806 if (isset($extbaseSettings['persistence']['classes'][$className]) && !empty($extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'])) {
807 return (int)$extbaseSettings['persistence']['classes'][$className]['newRecordStoragePid'];
808 } else {
809 $storagePidList = t3lib_div::intExplode(',', $extbaseSettings['persistence']['storagePid']);
810 return (int) $storagePidList[0];
811 }
812 }
813
814 }
815
816 ?>