2 namespace TYPO3\CMS\Extbase\Persistence\Generic
;
5 * This file is part of the TYPO3 CMS project.
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
;
18 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
;
19 use TYPO3\CMS\Extbase\Persistence\ObjectMonitoringInterface
;
22 * A persistence backend. This backend maps objects to the relational model of the storage backend.
23 * It persists all added, removed and changed objects.
25 class Backend
implements \TYPO3\CMS\Extbase\Persistence\Generic\BackendInterface
, \TYPO3\CMS\Core\SingletonInterface
28 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Session
33 * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
35 protected $persistenceManager;
38 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
40 protected $aggregateRootObjects;
43 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
45 protected $deletedEntities;
48 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
50 protected $changedEntities;
53 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
55 protected $visitedDuringPersistence;
58 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
60 protected $reflectionService;
63 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
65 protected $qomFactory;
68 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface
70 protected $storageBackend;
73 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
75 protected $dataMapper;
78 * The TYPO3 reference index object
80 * @var \TYPO3\CMS\Core\Database\ReferenceIndex
82 protected $referenceIndex;
85 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
87 protected $configurationManager;
90 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
92 protected $signalSlotDispatcher;
95 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Session $session
97 public function injectSession(\TYPO3\CMS\Extbase\Persistence\Generic\Session
$session)
99 $this->session
= $session;
103 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
105 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService
$reflectionService)
107 $this->reflectionService
= $reflectionService;
111 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory
113 public function injectQomFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
$qomFactory)
115 $this->qomFactory
= $qomFactory;
119 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface $storageBackend
121 public function injectStorageBackend(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface
$storageBackend)
123 $this->storageBackend
= $storageBackend;
127 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper
129 public function injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
$dataMapper)
131 $this->dataMapper
= $dataMapper;
135 * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
137 public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher
$signalSlotDispatcher)
139 $this->signalSlotDispatcher
= $signalSlotDispatcher;
143 * Constructs the backend
145 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
147 public function __construct(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
$configurationManager)
149 $this->configurationManager
= $configurationManager;
150 $this->referenceIndex
= \TYPO3\CMS\Core\Utility\GeneralUtility
::makeInstance(\TYPO3\CMS\Core\Database\ReferenceIndex
::class);
151 $this->aggregateRootObjects
= new \TYPO3\CMS\Extbase\Persistence\
ObjectStorage();
152 $this->deletedEntities
= new \TYPO3\CMS\Extbase\Persistence\
ObjectStorage();
153 $this->changedEntities
= new \TYPO3\CMS\Extbase\Persistence\
ObjectStorage();
157 * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager
159 public function setPersistenceManager(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
$persistenceManager)
161 $this->persistenceManager
= $persistenceManager;
165 * Returns the repository session
167 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Session
169 public function getSession()
171 return $this->session
;
175 * Returns the Data Mapper
177 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
179 public function getDataMapper()
181 return $this->dataMapper
;
185 * Returns the current QOM factory
187 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
189 public function getQomFactory()
191 return $this->qomFactory
;
195 * Returns the reflection service
197 * @return \TYPO3\CMS\Extbase\Reflection\ReflectionService
199 public function getReflectionService()
201 return $this->reflectionService
;
205 * Returns the number of records matching the query.
207 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
211 public function getObjectCountByQuery(\TYPO3\CMS\Extbase\Persistence\QueryInterface
$query)
213 return $this->storageBackend
->getObjectCountByQuery($query);
217 * Returns the object data matching the $query.
219 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
223 public function getObjectDataByQuery(\TYPO3\CMS\Extbase\Persistence\QueryInterface
$query)
225 $query = $this->emitBeforeGettingObjectDataSignal($query);
226 $result = $this->storageBackend
->getObjectDataByQuery($query);
227 $result = $this->emitAfterGettingObjectDataSignal($query, $result);
232 * Emits a signal before object data is fetched
234 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
235 * @return \TYPO3\CMS\Extbase\Persistence\QueryInterface Modified query
237 protected function emitBeforeGettingObjectDataSignal(\TYPO3\CMS\Extbase\Persistence\QueryInterface
$query)
239 $signalArguments = $this->signalSlotDispatcher
->dispatch(__CLASS__
, 'beforeGettingObjectData', [$query]);
240 return $signalArguments[0];
244 * Emits a signal after object data is fetched
246 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
247 * @param array $result
248 * @return array Modified result
250 protected function emitAfterGettingObjectDataSignal(\TYPO3\CMS\Extbase\Persistence\QueryInterface
$query, array $result)
252 $signalArguments = $this->signalSlotDispatcher
->dispatch(__CLASS__
, 'afterGettingObjectData', [$query, $result]);
253 return $signalArguments[1];
257 * Returns the (internal) identifier for the object, if it is known to the
258 * backend. Otherwise NULL is returned.
260 * @param object $object
261 * @return string|null The identifier for the object if it is known, or NULL
263 public function getIdentifierByObject($object)
265 if ($object instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy
) {
266 $object = $object->_loadRealInstance();
267 if (!is_object($object)) {
271 return $this->session
->getIdentifierByObject($object);
275 * Returns the object with the (internal) identifier, if it is known to the
276 * backend. Otherwise NULL is returned.
278 * @param string $identifier
279 * @param string $className
280 * @return object|null The object for the identifier if it is known, or NULL
282 public function getObjectByIdentifier($identifier, $className)
284 if ($this->session
->hasIdentifier($identifier, $className)) {
285 return $this->session
->getObjectByIdentifier($identifier, $className);
287 $query = $this->persistenceManager
->createQueryForType($className);
288 $query->getQuerySettings()->setRespectStoragePage(false
);
289 $query->getQuerySettings()->setRespectSysLanguage(false
);
290 return $query->matching($query->equals('uid', $identifier))->execute()->getFirst();
294 * Checks if the given object has ever been persisted.
296 * @param object $object The object to check
297 * @return bool TRUE if the object is new, FALSE if the object exists in the repository
299 public function isNewObject($object)
301 return $this->getIdentifierByObject($object) === null
;
305 * Sets the aggregate root objects
307 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects
309 public function setAggregateRootObjects(\TYPO3\CMS\Extbase\Persistence\ObjectStorage
$objects)
311 $this->aggregateRootObjects
= $objects;
315 * Sets the changed objects
317 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
319 public function setChangedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage
$entities)
321 $this->changedEntities
= $entities;
325 * Sets the deleted objects
327 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
329 public function setDeletedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage
$entities)
331 $this->deletedEntities
= $entities;
335 * Commits the current persistence session.
337 public function commit()
339 $this->persistObjects();
340 $this->processDeletedObjects();
344 * Traverse and persist all aggregate roots and their object graph.
346 protected function persistObjects()
348 $this->visitedDuringPersistence
= new \TYPO3\CMS\Extbase\Persistence\
ObjectStorage();
349 foreach ($this->aggregateRootObjects
as $object) {
350 /** @var DomainObjectInterface $object */
351 if ($object->_isNew()) {
352 $this->insertObject($object);
354 $this->persistObject($object, null
);
356 foreach ($this->changedEntities
as $object) {
357 $this->persistObject($object, null
);
362 * Persists the given object.
364 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be inserted
366 protected function persistObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object)
368 if (isset($this->visitedDuringPersistence
[$object])) {
373 $dataMap = $this->dataMapper
->getDataMap(get_class($object));
374 $properties = $object->_getProperties();
375 foreach ($properties as $propertyName => $propertyValue) {
376 if (!$dataMap->isPersistableProperty($propertyName) ||
$this->propertyValueIsLazyLoaded($propertyValue)) {
379 $columnMap = $dataMap->getColumnMap($propertyName);
380 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage
) {
381 $cleanProperty = $object->_getCleanProperty($propertyName);
382 // objectstorage needs to be persisted if the object is new, the objectstorge is dirty, meaning it has
383 // been changed after initial build, or an empty objectstorge is present and the cleanstate objectstorage
384 // has childelements, meaning all elements should been removed from the objectstorage
385 if ($object->_isNew() ||
$propertyValue->_isDirty() ||
($propertyValue->count() === 0 && $cleanProperty && $cleanProperty->count() > 0)) {
386 $this->persistObjectStorage($propertyValue, $object, $propertyName, $row);
387 $propertyValue->_memorizeCleanState();
389 foreach ($propertyValue as $containedObject) {
390 if ($containedObject instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
) {
391 $queue[] = $containedObject;
394 } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
395 && $object instanceof ObjectMonitoringInterface
) {
396 if ($object->_isDirty($propertyName)) {
397 if ($propertyValue->_isNew()) {
398 $this->insertObject($propertyValue, $object, $propertyName);
400 // Check explicitly for NULL, as getPlainValue would convert this to 'NULL'
401 $row[$columnMap->getColumnName()] = $propertyValue !== null
402 ?
$this->dataMapper
->getPlainValue($propertyValue)
405 $queue[] = $propertyValue;
406 } elseif ($object->_isNew() ||
$object->_isDirty($propertyName)) {
407 $row[$columnMap->getColumnName()] = $this->dataMapper
->getPlainValue($propertyValue, $columnMap);
411 $this->updateObject($object, $row);
412 $object->_memorizeCleanState();
414 $this->visitedDuringPersistence
[$object] = $object->getUid();
415 foreach ($queue as $queuedObject) {
416 $this->persistObject($queuedObject);
418 $this->emitAfterPersistObjectSignal($object);
422 * Checks, if the property value is lazy loaded and was not initialized
424 * @param mixed $propertyValue The property value
427 protected function propertyValueIsLazyLoaded($propertyValue)
429 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy
) {
432 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage
) {
433 if ($propertyValue->isInitialized() === false
) {
441 * Persists an object storage. Objects of a 1:n or m:n relation are queued and processed with the parent object. A 1:1 relation
442 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
443 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
445 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objectStorage The object storage to be persisted.
446 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object. One of the properties holds the object storage.
447 * @param string $propertyName The name of the property holding the object storage.
448 * @param array &$row The row array of the parent object to be persisted. It's passed by reference and gets filled with either a comma separated list of uids (csv) or the number of contained objects.
450 protected function persistObjectStorage(\TYPO3\CMS\Extbase\Persistence\ObjectStorage
$objectStorage, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$parentObject, $propertyName, array &$row)
452 $className = get_class($parentObject);
453 $columnMap = $this->dataMapper
->getDataMap($className)->getColumnMap($propertyName);
454 $propertyMetaData = $this->reflectionService
->getClassSchema($className)->getProperty($propertyName);
455 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
456 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
457 if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_MANY
&& $propertyMetaData['cascade'] === 'remove') {
458 $this->removeEntity($removedObject);
463 $sortingPosition = 1;
464 $updateSortingOfFollowing = false
;
466 foreach ($objectStorage as $object) {
467 /** @var DomainObjectInterface $object */
468 if (empty($currentUids)) {
469 $sortingPosition = 1;
473 $cleanProperty = $parentObject->_getCleanProperty($propertyName);
474 if ($object->_isNew()) {
475 $this->insertObject($object);
476 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
477 // if a new object is inserted, all objects after this need to have their sorting updated
478 $updateSortingOfFollowing = true
;
479 } elseif ($cleanProperty === null ||
$cleanProperty->getPosition($object) === null
) {
480 // if parent object is new then it doesn't have cleanProperty yet; before attaching object it's clean position is null
481 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
482 // if a relation is dirty (speaking the same object is removed and added again at a different position), all objects after this needs to be updated the sorting
483 $updateSortingOfFollowing = true
;
484 } elseif ($objectStorage->isRelationDirty($object) ||
$cleanProperty->getPosition($object) !== $objectStorage->getPosition($object)) {
485 $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
486 $updateSortingOfFollowing = true
;
487 } elseif ($updateSortingOfFollowing) {
488 if ($sortingPosition > $objectStorage->getPosition($object)) {
489 $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
491 $sortingPosition = $objectStorage->getPosition($object);
494 $currentUids[] = $object->getUid();
497 if ($columnMap->getParentKeyFieldName() === null
) {
498 $row[$columnMap->getColumnName()] = implode(',', $currentUids);
500 $row[$columnMap->getColumnName()] = $this->dataMapper
->countRelated($parentObject, $propertyName);
505 * Returns the removed objects determined by a comparison of the clean property value
506 * with the actual property value.
508 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object
509 * @param string $propertyName
510 * @return array An array of removed objects
512 protected function getRemovedChildObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, $propertyName)
514 $removedObjects = [];
515 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
516 if (is_array($cleanPropertyValue) ||
$cleanPropertyValue instanceof \Iterator
) {
517 $propertyValue = $object->_getProperty($propertyName);
518 foreach ($cleanPropertyValue as $containedObject) {
519 if (!$propertyValue->contains($containedObject)) {
520 $removedObjects[] = $containedObject;
524 return $removedObjects;
528 * Updates the fields defining the relation between the object and the parent object.
530 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
531 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
532 * @param string $parentPropertyName
533 * @param int $sortingPosition
535 protected function attachObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$parentObject, $parentPropertyName, $sortingPosition = 0)
537 $parentDataMap = $this->dataMapper
->getDataMap(get_class($parentObject));
538 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
539 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_MANY
) {
540 $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
541 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
) {
542 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
547 * Updates the fields defining the relation between the object and the parent object.
549 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
550 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject
551 * @param string $parentPropertyName
552 * @param int $sortingPosition
554 protected function updateRelationOfObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
$parentObject, $parentPropertyName, $sortingPosition = 0)
556 $parentDataMap = $this->dataMapper
->getDataMap(get_class($parentObject));
557 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
558 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_MANY
) {
559 $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
560 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
) {
561 $this->updateRelationInRelationTable($object, $parentObject, $parentPropertyName, $sortingPosition);
566 * Updates fields defining the relation between the object and the parent object in relation has-many.
568 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
569 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject
570 * @param string $parentPropertyName
571 * @param int $sortingPosition
572 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
574 protected function attachObjectToParentObjectRelationHasMany(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
$parentObject, $parentPropertyName, $sortingPosition = 0)
576 $parentDataMap = $this->dataMapper
->getDataMap(get_class($parentObject));
577 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
578 if ($parentColumnMap->getTypeOfRelation() !== \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_MANY
) {
579 throw new \TYPO3\CMS\Extbase\Persistence\Exception\
IllegalRelationTypeException(
580 'Parent column relation type is ' . $parentColumnMap->getTypeOfRelation() .
581 ' but should be ' . \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_MANY
,
586 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
587 if ($parentKeyFieldName !== null
) {
588 $row[$parentKeyFieldName] = $parentObject->getUid();
589 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
590 if ($parentTableFieldName !== null
) {
591 $row[$parentTableFieldName] = $parentDataMap->getTableName();
593 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
594 if (is_array($relationTableMatchFields)) {
595 $row = array_merge($relationTableMatchFields, $row);
598 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
599 if (!empty($childSortByFieldName)) {
600 $row[$childSortByFieldName] = $sortingPosition;
603 $this->updateObject($object, $row);
608 * Updates the fields defining the relation between the object and the parent object.
610 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
611 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
612 * @param string $parentPropertyName
614 protected function detachObjectFromParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$parentObject, $parentPropertyName)
616 $parentDataMap = $this->dataMapper
->getDataMap(get_class($parentObject));
617 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
618 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_MANY
) {
620 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
621 if ($parentKeyFieldName !== null
) {
622 $row[$parentKeyFieldName] = 0;
623 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
624 if ($parentTableFieldName !== null
) {
625 $row[$parentTableFieldName] = '';
627 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
628 if (is_array($relationTableMatchFields) && !empty($relationTableMatchFields)) {
629 $row = array_merge(array_fill_keys(array_keys($relationTableMatchFields), ''), $row);
632 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
633 if (!empty($childSortByFieldName)) {
634 $row[$childSortByFieldName] = 0;
637 $this->updateObject($object, $row);
639 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
) {
640 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
645 * Inserts an object in the storage backend
647 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be insterted in the storage
648 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parentobject.
649 * @param string $parentPropertyName
651 protected function insertObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$parentObject = null
, $parentPropertyName = '')
653 if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject
) {
654 $result = $this->getUidOfAlreadyPersistedValueObject($object);
655 if ($result !== false
) {
656 $object->_setProperty('uid', (int)$result);
660 $dataMap = $this->dataMapper
->getDataMap(get_class($object));
662 $properties = $object->_getProperties();
663 foreach ($properties as $propertyName => $propertyValue) {
664 if (!$dataMap->isPersistableProperty($propertyName) ||
$this->propertyValueIsLazyLoaded($propertyValue)) {
667 $columnMap = $dataMap->getColumnMap($propertyName);
668 if ($columnMap->getTypeOfRelation() === ColumnMap
::RELATION_HAS_ONE
) {
669 $row[$columnMap->getColumnName()] = 0;
670 } elseif ($columnMap->getTypeOfRelation() !== ColumnMap
::RELATION_NONE
) {
671 if ($columnMap->getParentKeyFieldName() === null
) {
673 $row[$columnMap->getColumnName()] = '';
676 $row[$columnMap->getColumnName()] = 0;
678 } elseif ($propertyValue !== null
) {
679 $row[$columnMap->getColumnName()] = $this->dataMapper
->getPlainValue($propertyValue, $columnMap);
682 $this->addCommonFieldsToRow($object, $row);
683 if ($dataMap->getLanguageIdColumnName() !== null
&& $object->_getProperty('_languageUid') === null
) {
684 $row[$dataMap->getLanguageIdColumnName()] = 0;
685 $object->_setProperty('_languageUid', 0);
687 if ($dataMap->getTranslationOriginColumnName() !== null
) {
688 $row[$dataMap->getTranslationOriginColumnName()] = 0;
690 if ($dataMap->getTranslationOriginDiffSourceName() !== null
) {
691 $row[$dataMap->getTranslationOriginDiffSourceName()] = '';
693 if ($parentObject !== null
&& $parentPropertyName) {
694 $parentColumnDataMap = $this->dataMapper
->getDataMap(get_class($parentObject))->getColumnMap($parentPropertyName);
695 $relationTableMatchFields = $parentColumnDataMap->getRelationTableMatchFields();
696 if (is_array($relationTableMatchFields)) {
697 $row = array_merge($relationTableMatchFields, $row);
699 if ($parentColumnDataMap->getParentKeyFieldName() !== null
) {
700 $row[$parentColumnDataMap->getParentKeyFieldName()] = (int)$parentObject->getUid();
703 $uid = $this->storageBackend
->addRow($dataMap->getTableName(), $row);
704 $object->_setProperty('uid', (int)$uid);
705 $object->setPid((int)$row['pid']);
706 if ((int)$uid >= 1) {
707 $this->emitAfterInsertObjectSignal($object);
709 $frameworkConfiguration = $this->configurationManager
->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
::CONFIGURATION_TYPE_FRAMEWORK
);
710 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
711 $this->referenceIndex
->updateRefIndexTable($dataMap->getTableName(), $uid);
713 $this->session
->registerObject($object, $uid);
714 if ((int)$uid >= 1) {
715 $this->emitEndInsertObjectSignal($object);
720 * Emits a signal after an object was added to the storage
722 * @param DomainObjectInterface $object
724 protected function emitAfterInsertObjectSignal(DomainObjectInterface
$object)
726 $this->signalSlotDispatcher
->dispatch(__CLASS__
, 'afterInsertObject', [$object]);
730 * Emits a signal after an object was registered in persistence session
731 * This signal replaces the afterInsertObject signal which is now deprecated
733 * @param DomainObjectInterface $object
735 protected function emitEndInsertObjectSignal(DomainObjectInterface
$object)
737 $this->signalSlotDispatcher
->dispatch(__CLASS__
, 'endInsertObject', [$object]);
741 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
743 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object The object to be tested
744 * @return mixed The matching uid if an object was found, else FALSE
746 protected function getUidOfAlreadyPersistedValueObject(\TYPO3\CMS\Extbase\DomainObject\AbstractValueObject
$object)
748 return $this->storageBackend
->getUidOfAlreadyPersistedValueObject($object);
752 * Inserts mm-relation into a relation table
754 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
755 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
756 * @param string $propertyName The name of the parent object's property where the related objects are stored in
757 * @param int $sortingPosition Defaults to NULL
758 * @return int The uid of the inserted row
760 protected function insertRelationInRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$parentObject, $propertyName, $sortingPosition = null
)
762 $dataMap = $this->dataMapper
->getDataMap(get_class($parentObject));
763 $columnMap = $dataMap->getColumnMap($propertyName);
764 $parentUid = $parentObject->getUid();
765 if ($parentObject->_getProperty('_localizedUid') !== null
) {
766 $parentUid = $parentObject->_getProperty('_localizedUid');
769 $columnMap->getParentKeyFieldName() => (int)$parentUid,
770 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
771 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ?
(int)$sortingPosition : 0
773 $relationTableName = $columnMap->getRelationTableName();
774 if ($columnMap->getRelationTablePageIdColumnName() !== null
) {
775 $row[$columnMap->getRelationTablePageIdColumnName()] = $this->determineStoragePageIdForNewRecord();
777 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
778 if (is_array($relationTableMatchFields)) {
779 $row = array_merge($relationTableMatchFields, $row);
781 $relationTableInsertFields = $columnMap->getRelationTableInsertFields();
782 if (is_array($relationTableInsertFields)) {
783 $row = array_merge($relationTableInsertFields, $row);
785 $res = $this->storageBackend
->addRow($relationTableName, $row, true
);
790 * Updates mm-relation in a relation table
792 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
793 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
794 * @param string $propertyName The name of the parent object's property where the related objects are stored in
795 * @param int $sortingPosition Defaults to NULL
796 * @return bool TRUE if update was successfully
798 protected function updateRelationInRelationTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$parentObject, $propertyName, $sortingPosition = 0)
800 $dataMap = $this->dataMapper
->getDataMap(get_class($parentObject));
801 $columnMap = $dataMap->getColumnMap($propertyName);
803 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
804 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
805 $columnMap->getChildSortByFieldName() => (int)$sortingPosition
807 $relationTableName = $columnMap->getRelationTableName();
808 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
809 if (is_array($relationTableMatchFields)) {
810 $row = array_merge($relationTableMatchFields, $row);
812 $res = $this->storageBackend
->updateRelationTableRow(
820 * Delete all mm-relations of a parent from a relation table
822 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
823 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
824 * @return bool TRUE if delete was successfully
826 protected function deleteAllRelationsFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$parentObject, $parentPropertyName)
828 $dataMap = $this->dataMapper
->getDataMap(get_class($parentObject));
829 $columnMap = $dataMap->getColumnMap($parentPropertyName);
830 $relationTableName = $columnMap->getRelationTableName();
831 $relationMatchFields = [
832 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
834 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
835 if (is_array($relationTableMatchFields)) {
836 $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields);
838 $res = $this->storageBackend
->removeRow($relationTableName, $relationMatchFields, false
);
843 * Delete an mm-relation from a relation table
845 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $relatedObject The related object
846 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
847 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
850 protected function deleteRelationFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$relatedObject, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$parentObject, $parentPropertyName)
852 $dataMap = $this->dataMapper
->getDataMap(get_class($parentObject));
853 $columnMap = $dataMap->getColumnMap($parentPropertyName);
854 $relationTableName = $columnMap->getRelationTableName();
855 $relationMatchFields = [
856 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
857 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid()
859 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
860 if (is_array($relationTableMatchFields)) {
861 $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields);
863 $res = $this->storageBackend
->removeRow($relationTableName, $relationMatchFields, false
);
868 * Fetches maximal value currently used for sorting field in parent table
870 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
871 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
872 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
873 * @return mixed the max value
875 protected function fetchMaxSortingFromParentTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$parentObject, $parentPropertyName)
877 $parentDataMap = $this->dataMapper
->getDataMap(get_class($parentObject));
878 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
879 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_MANY
) {
880 $tableName = $parentColumnMap->getChildTableName();
881 $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
883 if (empty($sortByFieldName)) {
887 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
888 if ($parentKeyFieldName !== null
) {
889 $matchFields[$parentKeyFieldName] = $parentObject->getUid();
890 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
891 if ($parentTableFieldName !== null
) {
892 $matchFields[$parentTableFieldName] = $parentDataMap->getTableName();
896 if (empty($matchFields)) {
899 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
) {
900 $tableName = $parentColumnMap->getRelationTableName();
901 $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
904 $parentColumnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
907 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
908 if (is_array($relationTableMatchFields)) {
909 $matchFields = array_merge($relationTableMatchFields, $matchFields);
912 throw new \TYPO3\CMS\Extbase\Persistence\Exception\
IllegalRelationTypeException('Unexpected parent column relation type: ' . $parentColumnMap->getTypeOfRelation(), 1345368106);
915 $result = $this->storageBackend
->getMaxValueFromTable(
924 * Updates a given object in the storage
926 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be updated
927 * @param array $row Row to be stored
930 protected function updateObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, array $row)
932 $dataMap = $this->dataMapper
->getDataMap(get_class($object));
933 $this->addCommonFieldsToRow($object, $row);
934 $row['uid'] = $object->getUid();
935 if ($dataMap->getLanguageIdColumnName() !== null
) {
936 $row[$dataMap->getLanguageIdColumnName()] = (int)$object->_getProperty('_languageUid');
937 if ($object->_getProperty('_localizedUid') !== null
) {
938 $row['uid'] = $object->_getProperty('_localizedUid');
941 $res = $this->storageBackend
->updateRow($dataMap->getTableName(), $row);
943 $this->emitAfterUpdateObjectSignal($object);
945 $frameworkConfiguration = $this->configurationManager
->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
::CONFIGURATION_TYPE_FRAMEWORK
);
946 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
947 $this->referenceIndex
->updateRefIndexTable($dataMap->getTableName(), $row['uid']);
953 * Emits a signal after an object was updated in storage
955 * @param DomainObjectInterface $object
957 protected function emitAfterUpdateObjectSignal(DomainObjectInterface
$object)
959 $this->signalSlotDispatcher
->dispatch(__CLASS__
, 'afterUpdateObject', [$object]);
963 * Emits a signal after an object was persisted
965 * @param DomainObjectInterface $object
967 protected function emitAfterPersistObjectSignal(DomainObjectInterface
$object)
969 $this->signalSlotDispatcher
->dispatch(__CLASS__
, 'afterPersistObject', [$object]);
973 * Adds common databse fields to a row
975 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
978 protected function addCommonFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, array &$row)
980 $dataMap = $this->dataMapper
->getDataMap(get_class($object));
981 $this->addCommonDateFieldsToRow($object, $row);
982 if ($dataMap->getRecordTypeColumnName() !== null
&& $dataMap->getRecordType() !== null
) {
983 $row[$dataMap->getRecordTypeColumnName()] = $dataMap->getRecordType();
985 if ($object->_isNew() && !isset($row['pid'])) {
986 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
991 * Adjustes the common date fields of the given row to the current time
993 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
994 * @param array &$row The row to be updated
996 protected function addCommonDateFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, array &$row)
998 $dataMap = $this->dataMapper
->getDataMap(get_class($object));
999 if ($object->_isNew() && $dataMap->getCreationDateColumnName() !== null
) {
1000 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
1002 if ($dataMap->getModificationDateColumnName() !== null
) {
1003 $row[$dataMap->getModificationDateColumnName()] = $GLOBALS['EXEC_TIME'];
1008 * Iterate over deleted aggregate root objects and process them
1010 protected function processDeletedObjects()
1012 foreach ($this->deletedEntities
as $entity) {
1013 if ($this->session
->hasObject($entity)) {
1014 $this->removeEntity($entity);
1015 $this->session
->unregisterReconstitutedEntity($entity);
1016 $this->session
->unregisterObject($entity);
1019 $this->deletedEntities
= new \TYPO3\CMS\Extbase\Persistence\
ObjectStorage();
1025 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be removed from the storage
1026 * @param bool $markAsDeleted Whether to just flag the row deleted (default) or really delete it
1028 protected function removeEntity(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object, $markAsDeleted = true
)
1030 $dataMap = $this->dataMapper
->getDataMap(get_class($object));
1031 $tableName = $dataMap->getTableName();
1032 if ($markAsDeleted === true
&& $dataMap->getDeletedFlagColumnName() !== null
) {
1033 $deletedColumnName = $dataMap->getDeletedFlagColumnName();
1035 'uid' => $object->getUid(),
1036 $deletedColumnName => 1
1038 $this->addCommonDateFieldsToRow($object, $row);
1039 $res = $this->storageBackend
->updateRow($tableName, $row);
1041 $res = $this->storageBackend
->removeRow($tableName, ['uid' => $object->getUid()]);
1043 if ($res === true
) {
1044 $this->emitAfterRemoveObjectSignal($object);
1046 $this->removeRelatedObjects($object);
1047 $frameworkConfiguration = $this->configurationManager
->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
::CONFIGURATION_TYPE_FRAMEWORK
);
1048 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
1049 $this->referenceIndex
->updateRefIndexTable($tableName, $object->getUid());
1054 * Emits a signal after an object was removed from storage
1056 * @param DomainObjectInterface $object
1058 protected function emitAfterRemoveObjectSignal(DomainObjectInterface
$object)
1060 $this->signalSlotDispatcher
->dispatch(__CLASS__
, 'afterRemoveObject', [$object]);
1064 * Remove related objects
1066 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to scanned for related objects
1068 protected function removeRelatedObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object)
1070 $className = get_class($object);
1071 $dataMap = $this->dataMapper
->getDataMap($className);
1072 $classSchema = $this->reflectionService
->getClassSchema($className);
1073 $properties = $object->_getProperties();
1074 foreach ($properties as $propertyName => $propertyValue) {
1075 $columnMap = $dataMap->getColumnMap($propertyName);
1076 if ($columnMap === null
) {
1079 $propertyMetaData = $classSchema->getProperty($propertyName);
1080 if ($propertyMetaData['cascade'] === 'remove') {
1081 if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap
::RELATION_HAS_MANY
) {
1082 foreach ($propertyValue as $containedObject) {
1083 $this->removeEntity($containedObject);
1085 } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
) {
1086 $this->removeEntity($propertyValue);
1088 } elseif ($dataMap->getDeletedFlagColumnName() === null
1089 && $columnMap->getTypeOfRelation() === ColumnMap
::RELATION_HAS_AND_BELONGS_TO_MANY
1091 $this->deleteAllRelationsFromRelationtable($object, $propertyName);
1097 * Determine the storage page ID for a given NEW record
1099 * This does the following:
1100 * - If the domain object has an accessible property 'pid' (i.e. through a getPid() method), that is used to store the record.
1101 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
1102 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
1104 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
1105 * @return int the storage Page ID where the object should be stored
1107 protected function determineStoragePageIdForNewRecord(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
$object = null
)
1109 $frameworkConfiguration = $this->configurationManager
->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
::CONFIGURATION_TYPE_FRAMEWORK
);
1110 if ($object !== null
) {
1111 if (\TYPO3\CMS\Extbase\Reflection\ObjectAccess
::isPropertyGettable($object, 'pid')) {
1112 $pid = \TYPO3\CMS\Extbase\Reflection\ObjectAccess
::getProperty($object, 'pid');
1117 $className = get_class($object);
1118 if (isset($frameworkConfiguration['persistence']['classes'][$className]) && !empty($frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'])) {
1119 return (int)$frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'];
1122 $storagePidList = \TYPO3\CMS\Core\Utility\GeneralUtility
::intExplode(',', $frameworkConfiguration['persistence']['storagePid']);
1123 return (int)$storagePidList[0];