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