[BUGFIX] Remove relations from MM table
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Persistence / Generic / Backend.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence\Generic;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
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.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
18 use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap;
19 use TYPO3\CMS\Extbase\Persistence\ObjectMonitoringInterface;
20
21 /**
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.
24 */
25 class Backend implements \TYPO3\CMS\Extbase\Persistence\Generic\BackendInterface, \TYPO3\CMS\Core\SingletonInterface
26 {
27 /**
28 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Session
29 */
30 protected $session;
31
32 /**
33 * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
34 */
35 protected $persistenceManager;
36
37 /**
38 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
39 */
40 protected $aggregateRootObjects;
41
42 /**
43 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
44 */
45 protected $deletedEntities;
46
47 /**
48 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
49 */
50 protected $changedEntities;
51
52 /**
53 * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
54 */
55 protected $visitedDuringPersistence;
56
57 /**
58 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
59 */
60 protected $reflectionService;
61
62 /**
63 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
64 */
65 protected $qomFactory;
66
67 /**
68 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface
69 */
70 protected $storageBackend;
71
72 /**
73 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
74 */
75 protected $dataMapper;
76
77 /**
78 * The TYPO3 reference index object
79 *
80 * @var \TYPO3\CMS\Core\Database\ReferenceIndex
81 */
82 protected $referenceIndex;
83
84 /**
85 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
86 */
87 protected $configurationManager;
88
89 /**
90 * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
91 */
92 protected $signalSlotDispatcher;
93
94 /**
95 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Session $session
96 */
97 public function injectSession(\TYPO3\CMS\Extbase\Persistence\Generic\Session $session)
98 {
99 $this->session = $session;
100 }
101
102 /**
103 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
104 */
105 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
106 {
107 $this->reflectionService = $reflectionService;
108 }
109
110 /**
111 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory
112 */
113 public function injectQomFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory)
114 {
115 $this->qomFactory = $qomFactory;
116 }
117
118 /**
119 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface $storageBackend
120 */
121 public function injectStorageBackend(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface $storageBackend)
122 {
123 $this->storageBackend = $storageBackend;
124 }
125
126 /**
127 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper
128 */
129 public function injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)
130 {
131 $this->dataMapper = $dataMapper;
132 }
133
134 /**
135 * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
136 */
137 public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
138 {
139 $this->signalSlotDispatcher = $signalSlotDispatcher;
140 }
141
142 /**
143 * Constructs the backend
144 *
145 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
146 */
147 public function __construct(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
148 {
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();
154 }
155
156 /**
157 * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager
158 */
159 public function setPersistenceManager(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager)
160 {
161 $this->persistenceManager = $persistenceManager;
162 }
163
164 /**
165 * Returns the repository session
166 *
167 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Session
168 */
169 public function getSession()
170 {
171 return $this->session;
172 }
173
174 /**
175 * Returns the Data Mapper
176 *
177 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
178 */
179 public function getDataMapper()
180 {
181 return $this->dataMapper;
182 }
183
184 /**
185 * Returns the current QOM factory
186 *
187 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
188 */
189 public function getQomFactory()
190 {
191 return $this->qomFactory;
192 }
193
194 /**
195 * Returns the reflection service
196 *
197 * @return \TYPO3\CMS\Extbase\Reflection\ReflectionService
198 */
199 public function getReflectionService()
200 {
201 return $this->reflectionService;
202 }
203
204 /**
205 * Returns the number of records matching the query.
206 *
207 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
208 * @return int
209 * @api
210 */
211 public function getObjectCountByQuery(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query)
212 {
213 return $this->storageBackend->getObjectCountByQuery($query);
214 }
215
216 /**
217 * Returns the object data matching the $query.
218 *
219 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
220 * @return array
221 * @api
222 */
223 public function getObjectDataByQuery(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query)
224 {
225 $query = $this->emitBeforeGettingObjectDataSignal($query);
226 $result = $this->storageBackend->getObjectDataByQuery($query);
227 $result = $this->emitAfterGettingObjectDataSignal($query, $result);
228 return $result;
229 }
230
231 /**
232 * Emits a signal before object data is fetched
233 *
234 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
235 * @return \TYPO3\CMS\Extbase\Persistence\QueryInterface Modified query
236 */
237 protected function emitBeforeGettingObjectDataSignal(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query)
238 {
239 $signalArguments = $this->signalSlotDispatcher->dispatch(__CLASS__, 'beforeGettingObjectData', [$query]);
240 return $signalArguments[0];
241 }
242
243 /**
244 * Emits a signal after object data is fetched
245 *
246 * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
247 * @param array $result
248 * @return array Modified result
249 */
250 protected function emitAfterGettingObjectDataSignal(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query, array $result)
251 {
252 $signalArguments = $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterGettingObjectData', [$query, $result]);
253 return $signalArguments[1];
254 }
255
256 /**
257 * Returns the (internal) identifier for the object, if it is known to the
258 * backend. Otherwise NULL is returned.
259 *
260 * @param object $object
261 * @return string|NULL The identifier for the object if it is known, or NULL
262 */
263 public function getIdentifierByObject($object)
264 {
265 if ($object instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
266 $object = $object->_loadRealInstance();
267 if (!is_object($object)) {
268 return null;
269 }
270 }
271 return $this->session->getIdentifierByObject($object);
272 }
273
274 /**
275 * Returns the object with the (internal) identifier, if it is known to the
276 * backend. Otherwise NULL is returned.
277 *
278 * @param string $identifier
279 * @param string $className
280 * @return object|NULL The object for the identifier if it is known, or NULL
281 */
282 public function getObjectByIdentifier($identifier, $className)
283 {
284 if ($this->session->hasIdentifier($identifier, $className)) {
285 return $this->session->getObjectByIdentifier($identifier, $className);
286 } else {
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();
291 }
292 }
293
294 /**
295 * Checks if the given object has ever been persisted.
296 *
297 * @param object $object The object to check
298 * @return bool TRUE if the object is new, FALSE if the object exists in the repository
299 */
300 public function isNewObject($object)
301 {
302 return $this->getIdentifierByObject($object) === null;
303 }
304
305 /**
306 * Sets the aggregate root objects
307 *
308 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects
309 * @return void
310 */
311 public function setAggregateRootObjects(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects)
312 {
313 $this->aggregateRootObjects = $objects;
314 }
315
316 /**
317 * Sets the changed objects
318 *
319 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
320 * @return void
321 */
322 public function setChangedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities)
323 {
324 $this->changedEntities = $entities;
325 }
326
327 /**
328 * Sets the deleted objects
329 *
330 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
331 * @return void
332 */
333 public function setDeletedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities)
334 {
335 $this->deletedEntities = $entities;
336 }
337
338 /**
339 * Commits the current persistence session.
340 *
341 * @return void
342 */
343 public function commit()
344 {
345 $this->persistObjects();
346 $this->processDeletedObjects();
347 }
348
349 /**
350 * Traverse and persist all aggregate roots and their object graph.
351 *
352 * @return void
353 */
354 protected function persistObjects()
355 {
356 $this->visitedDuringPersistence = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
357 foreach ($this->aggregateRootObjects as $object) {
358 /** @var DomainObjectInterface $object */
359 if ($object->_isNew()) {
360 $this->insertObject($object);
361 }
362 $this->persistObject($object, null);
363 }
364 foreach ($this->changedEntities as $object) {
365 $this->persistObject($object, null);
366 }
367 }
368
369 /**
370 * Persists the given object.
371 *
372 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be inserted
373 * @return void
374 */
375 protected function persistObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object)
376 {
377 if (isset($this->visitedDuringPersistence[$object])) {
378 return;
379 }
380 $row = [];
381 $queue = [];
382 $dataMap = $this->dataMapper->getDataMap(get_class($object));
383 $properties = $object->_getProperties();
384 foreach ($properties as $propertyName => $propertyValue) {
385 if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) {
386 continue;
387 }
388 $columnMap = $dataMap->getColumnMap($propertyName);
389 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage) {
390 $cleanProperty = $object->_getCleanProperty($propertyName);
391 // objectstorage needs to be persisted if the object is new, the objectstorge is dirty, meaning it has
392 // been changed after initial build, or an empty objectstorge is present and the cleanstate objectstorage
393 // has childelements, meaning all elements should been removed from the objectstorage
394 if ($object->_isNew() || $propertyValue->_isDirty() || ($propertyValue->count() === 0 && $cleanProperty && $cleanProperty->count() > 0)) {
395 $this->persistObjectStorage($propertyValue, $object, $propertyName, $row);
396 $propertyValue->_memorizeCleanState();
397 }
398 foreach ($propertyValue as $containedObject) {
399 if ($containedObject instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
400 $queue[] = $containedObject;
401 }
402 }
403 } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
404 && $object instanceof ObjectMonitoringInterface) {
405 if ($object->_isDirty($propertyName)) {
406 if ($propertyValue->_isNew()) {
407 $this->insertObject($propertyValue, $object, $propertyName);
408 }
409 // Check explicitly for NULL, as getPlainValue would convert this to 'NULL'
410 $row[$columnMap->getColumnName()] = $propertyValue !== null
411 ? $this->dataMapper->getPlainValue($propertyValue)
412 : null;
413 }
414 $queue[] = $propertyValue;
415 } elseif ($object->_isNew() || $object->_isDirty($propertyName)) {
416 $row[$columnMap->getColumnName()] = $this->dataMapper->getPlainValue($propertyValue, $columnMap);
417 }
418 }
419 if (!empty($row)) {
420 $this->updateObject($object, $row);
421 $object->_memorizeCleanState();
422 }
423 $this->visitedDuringPersistence[$object] = $object->getUid();
424 foreach ($queue as $queuedObject) {
425 $this->persistObject($queuedObject);
426 }
427 $this->emitAfterPersistObjectSignal($object);
428 }
429
430 /**
431 * Checks, if the property value is lazy loaded and was not initialized
432 *
433 * @param mixed $propertyValue The property value
434 * @return bool
435 */
436 protected function propertyValueIsLazyLoaded($propertyValue)
437 {
438 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
439 return true;
440 }
441 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage) {
442 if ($propertyValue->isInitialized() === false) {
443 return true;
444 }
445 }
446 return false;
447 }
448
449 /**
450 * 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
451 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
452 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
453 *
454 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objectStorage The object storage to be persisted.
455 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object. One of the properties holds the object storage.
456 * @param string $propertyName The name of the property holding the object storage.
457 * @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.
458 * @return void
459 */
460 protected function persistObjectStorage(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $objectStorage, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, array &$row)
461 {
462 $className = get_class($parentObject);
463 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
464 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
465 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
466 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
467 if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
468 $this->removeEntity($removedObject);
469 }
470 }
471
472 $currentUids = [];
473 $sortingPosition = 1;
474 $updateSortingOfFollowing = false;
475
476 foreach ($objectStorage as $object) {
477 /** @var DomainObjectInterface $object */
478 if (empty($currentUids)) {
479 $sortingPosition = 1;
480 } else {
481 $sortingPosition++;
482 }
483 $cleanProperty = $parentObject->_getCleanProperty($propertyName);
484 if ($object->_isNew()) {
485 $this->insertObject($object);
486 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
487 // if a new object is inserted, all objects after this need to have their sorting updated
488 $updateSortingOfFollowing = true;
489 } elseif ($cleanProperty === null || $cleanProperty->getPosition($object) === null) {
490 // if parent object is new then it doesn't have cleanProperty yet; before attaching object it's clean position is null
491 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
492 // 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
493 $updateSortingOfFollowing = true;
494 } elseif ($objectStorage->isRelationDirty($object) || $cleanProperty->getPosition($object) !== $objectStorage->getPosition($object)) {
495 $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
496 $updateSortingOfFollowing = true;
497 } elseif ($updateSortingOfFollowing) {
498 if ($sortingPosition > $objectStorage->getPosition($object)) {
499 $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
500 } else {
501 $sortingPosition = $objectStorage->getPosition($object);
502 }
503 }
504 $currentUids[] = $object->getUid();
505 }
506
507 if ($columnMap->getParentKeyFieldName() === null) {
508 $row[$columnMap->getColumnName()] = implode(',', $currentUids);
509 } else {
510 $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
511 }
512 }
513
514 /**
515 * Returns the removed objects determined by a comparison of the clean property value
516 * with the actual property value.
517 *
518 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object
519 * @param string $propertyName
520 * @return array An array of removed objects
521 */
522 protected function getRemovedChildObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, $propertyName)
523 {
524 $removedObjects = [];
525 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
526 if (is_array($cleanPropertyValue) || $cleanPropertyValue instanceof \Iterator) {
527 $propertyValue = $object->_getProperty($propertyName);
528 foreach ($cleanPropertyValue as $containedObject) {
529 if (!$propertyValue->contains($containedObject)) {
530 $removedObjects[] = $containedObject;
531 }
532 }
533 }
534 return $removedObjects;
535 }
536
537 /**
538 * Updates the fields defining the relation between the object and the parent object.
539 *
540 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
541 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
542 * @param string $parentPropertyName
543 * @param int $sortingPosition
544 * @return void
545 */
546 protected function attachObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName, $sortingPosition = 0)
547 {
548 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
549 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
550 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
551 $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
552 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
553 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
554 }
555 }
556
557 /**
558 * Updates the fields defining the relation between the object and the parent object.
559 *
560 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
561 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject
562 * @param string $parentPropertyName
563 * @param int $sortingPosition
564 * @return void
565 */
566 protected function updateRelationOfObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0)
567 {
568 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
569 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
570 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
571 $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
572 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
573 $this->updateRelationInRelationTable($object, $parentObject, $parentPropertyName, $sortingPosition);
574 }
575 }
576
577 /**
578 * Updates fields defining the relation between the object and the parent object in relation has-many.
579 *
580 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
581 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject
582 * @param string $parentPropertyName
583 * @param int $sortingPosition
584 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
585 * @return void
586 */
587 protected function attachObjectToParentObjectRelationHasMany(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0)
588 {
589 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
590 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
591 if ($parentColumnMap->getTypeOfRelation() !== \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
592 throw new \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException(
593 'Parent column relation type is ' . $parentColumnMap->getTypeOfRelation() .
594 ' but should be ' . \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY,
595 1345368105
596 );
597 }
598 $row = [];
599 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
600 if ($parentKeyFieldName !== null) {
601 $row[$parentKeyFieldName] = $parentObject->getUid();
602 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
603 if ($parentTableFieldName !== null) {
604 $row[$parentTableFieldName] = $parentDataMap->getTableName();
605 }
606 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
607 if (is_array($relationTableMatchFields)) {
608 $row = array_merge($relationTableMatchFields, $row);
609 }
610 }
611 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
612 if (!empty($childSortByFieldName)) {
613 $row[$childSortByFieldName] = $sortingPosition;
614 }
615 if (!empty($row)) {
616 $this->updateObject($object, $row);
617 }
618 }
619
620 /**
621 * Updates the fields defining the relation between the object and the parent object.
622 *
623 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
624 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
625 * @param string $parentPropertyName
626 * @return void
627 */
628 protected function detachObjectFromParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
629 {
630 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
631 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
632 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
633 $row = [];
634 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
635 if ($parentKeyFieldName !== null) {
636 $row[$parentKeyFieldName] = 0;
637 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
638 if ($parentTableFieldName !== null) {
639 $row[$parentTableFieldName] = '';
640 }
641 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
642 if (is_array($relationTableMatchFields) && !empty($relationTableMatchFields)) {
643 $row = array_merge(array_fill_keys(array_keys($relationTableMatchFields), ''), $row);
644 }
645 }
646 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
647 if (!empty($childSortByFieldName)) {
648 $row[$childSortByFieldName] = 0;
649 }
650 if (!empty($row)) {
651 $this->updateObject($object, $row);
652 }
653 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
654 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
655 }
656 }
657
658 /**
659 * Inserts an object in the storage backend
660 *
661 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be insterted in the storage
662 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parentobject.
663 * @param string $parentPropertyName
664 * @return void
665 */
666 protected function insertObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject = null, $parentPropertyName = '')
667 {
668 if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject) {
669 $result = $this->getUidOfAlreadyPersistedValueObject($object);
670 if ($result !== false) {
671 $object->_setProperty('uid', (int)$result);
672 return;
673 }
674 }
675 $dataMap = $this->dataMapper->getDataMap(get_class($object));
676 $row = [];
677 $properties = $object->_getProperties();
678 foreach ($properties as $propertyName => $propertyValue) {
679 if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) {
680 continue;
681 }
682 $columnMap = $dataMap->getColumnMap($propertyName);
683 if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_ONE) {
684 $row[$columnMap->getColumnName()] = 0;
685 } elseif ($columnMap->getTypeOfRelation() !== ColumnMap::RELATION_NONE) {
686 if ($columnMap->getParentKeyFieldName() === null) {
687 // CSV type relation
688 $row[$columnMap->getColumnName()] = '';
689 } else {
690 // MM type relation
691 $row[$columnMap->getColumnName()] = 0;
692 }
693 } elseif ($propertyValue !== null) {
694 $row[$columnMap->getColumnName()] = $this->dataMapper->getPlainValue($propertyValue, $columnMap);
695 }
696 }
697 $this->addCommonFieldsToRow($object, $row);
698 if ($dataMap->getLanguageIdColumnName() !== null && $object->_getProperty('_languageUid') === null) {
699 $row[$dataMap->getLanguageIdColumnName()] = 0;
700 $object->_setProperty('_languageUid', 0);
701 }
702 if ($dataMap->getTranslationOriginColumnName() !== null) {
703 $row[$dataMap->getTranslationOriginColumnName()] = 0;
704 }
705 if ($dataMap->getTranslationOriginDiffSourceName() !== null) {
706 $row[$dataMap->getTranslationOriginDiffSourceName()] = '';
707 }
708 if ($parentObject !== null && $parentPropertyName) {
709 $parentColumnDataMap = $this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($parentPropertyName);
710 $relationTableMatchFields = $parentColumnDataMap->getRelationTableMatchFields();
711 if (is_array($relationTableMatchFields)) {
712 $row = array_merge($relationTableMatchFields, $row);
713 }
714 if ($parentColumnDataMap->getParentKeyFieldName() !== null) {
715 $row[$parentColumnDataMap->getParentKeyFieldName()] = (int)$parentObject->getUid();
716 }
717 }
718 $uid = $this->storageBackend->addRow($dataMap->getTableName(), $row);
719 $object->_setProperty('uid', (int)$uid);
720 $object->setPid((int)$row['pid']);
721 if ((int)$uid >= 1) {
722 $this->emitAfterInsertObjectSignal($object);
723 }
724 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
725 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
726 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $uid);
727 }
728 $this->session->registerObject($object, $uid);
729 if ((int)$uid >= 1) {
730 $this->emitEndInsertObjectSignal($object);
731 }
732 }
733
734 /**
735 * Emits a signal after an object was added to the storage
736 *
737 * @param DomainObjectInterface $object
738 */
739 protected function emitAfterInsertObjectSignal(DomainObjectInterface $object)
740 {
741 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterInsertObject', [$object]);
742 }
743
744 /**
745 * Emits a signal after an object was registered in persistence session
746 * This signal replaces the afterInsertObject signal which is now deprecated
747 *
748 * @param DomainObjectInterface $object
749 */
750 protected function emitEndInsertObjectSignal(DomainObjectInterface $object)
751 {
752 $this->signalSlotDispatcher->dispatch(__CLASS__, 'endInsertObject', [$object]);
753 }
754
755 /**
756 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
757 *
758 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object The object to be tested
759 * @return mixed The matching uid if an object was found, else FALSE
760 */
761 protected function getUidOfAlreadyPersistedValueObject(\TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object)
762 {
763 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
764 }
765
766 /**
767 * Inserts mm-relation into a relation table
768 *
769 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
770 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
771 * @param string $propertyName The name of the parent object's property where the related objects are stored in
772 * @param int $sortingPosition Defaults to NULL
773 * @return int The uid of the inserted row
774 */
775 protected function insertRelationInRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, $sortingPosition = null)
776 {
777 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
778 $columnMap = $dataMap->getColumnMap($propertyName);
779 $parentUid = $parentObject->getUid();
780 if ($parentObject->_getProperty('_localizedUid') !== null) {
781 $parentUid = $parentObject->_getProperty('_localizedUid');
782 }
783 $row = [
784 $columnMap->getParentKeyFieldName() => (int)$parentUid,
785 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
786 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
787 ];
788 $relationTableName = $columnMap->getRelationTableName();
789 if ($columnMap->getRelationTablePageIdColumnName() !== null) {
790 $row[$columnMap->getRelationTablePageIdColumnName()] = $this->determineStoragePageIdForNewRecord();
791 }
792 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
793 if (is_array($relationTableMatchFields)) {
794 $row = array_merge($relationTableMatchFields, $row);
795 }
796 $relationTableInsertFields = $columnMap->getRelationTableInsertFields();
797 if (is_array($relationTableInsertFields)) {
798 $row = array_merge($relationTableInsertFields, $row);
799 }
800 $res = $this->storageBackend->addRow($relationTableName, $row, true);
801 return $res;
802 }
803
804 /**
805 * Updates mm-relation in a relation table
806 *
807 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
808 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
809 * @param string $propertyName The name of the parent object's property where the related objects are stored in
810 * @param int $sortingPosition Defaults to NULL
811 * @return bool TRUE if update was successfully
812 */
813 protected function updateRelationInRelationTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, $sortingPosition = 0)
814 {
815 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
816 $columnMap = $dataMap->getColumnMap($propertyName);
817 $row = [
818 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
819 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
820 $columnMap->getChildSortByFieldName() => (int)$sortingPosition
821 ];
822 $relationTableName = $columnMap->getRelationTableName();
823 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
824 if (is_array($relationTableMatchFields)) {
825 $row = array_merge($relationTableMatchFields, $row);
826 }
827 $res = $this->storageBackend->updateRelationTableRow(
828 $relationTableName,
829 $row);
830 return $res;
831 }
832
833 /**
834 * Delete all mm-relations of a parent from a relation table
835 *
836 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
837 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
838 * @return bool TRUE if delete was successfully
839 */
840 protected function deleteAllRelationsFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
841 {
842 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
843 $columnMap = $dataMap->getColumnMap($parentPropertyName);
844 $relationTableName = $columnMap->getRelationTableName();
845 $relationMatchFields = [
846 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
847 ];
848 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
849 if (is_array($relationTableMatchFields)) {
850 $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields);
851 }
852 $res = $this->storageBackend->removeRow($relationTableName, $relationMatchFields, false);
853 return $res;
854 }
855
856 /**
857 * Delete an mm-relation from a relation table
858 *
859 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $relatedObject The related object
860 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
861 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
862 * @return bool
863 */
864 protected function deleteRelationFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $relatedObject, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
865 {
866 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
867 $columnMap = $dataMap->getColumnMap($parentPropertyName);
868 $relationTableName = $columnMap->getRelationTableName();
869 $relationMatchFields = [
870 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
871 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid()
872 ];
873 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
874 if (is_array($relationTableMatchFields)) {
875 $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields);
876 }
877 $res = $this->storageBackend->removeRow($relationTableName, $relationMatchFields, false);
878 return $res;
879 }
880
881 /**
882 * Fetches maximal value currently used for sorting field in parent table
883 *
884 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
885 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
886 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
887 * @return mixed the max value
888 */
889 protected function fetchMaxSortingFromParentTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
890 {
891 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
892 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
893 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
894 $tableName = $parentColumnMap->getChildTableName();
895 $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
896
897 if (empty($sortByFieldName)) {
898 return false;
899 }
900 $matchFields = [];
901 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
902 if ($parentKeyFieldName !== null) {
903 $matchFields[$parentKeyFieldName] = $parentObject->getUid();
904 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
905 if ($parentTableFieldName !== null) {
906 $matchFields[$parentTableFieldName] = $parentDataMap->getTableName();
907 }
908 }
909
910 if (empty($matchFields)) {
911 return false;
912 }
913 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
914 $tableName = $parentColumnMap->getRelationTableName();
915 $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
916
917 $matchFields = [
918 $parentColumnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
919 ];
920
921 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
922 if (is_array($relationTableMatchFields)) {
923 $matchFields = array_merge($relationTableMatchFields, $matchFields);
924 }
925 } else {
926 throw new \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException('Unexpected parent column relation type: ' . $parentColumnMap->getTypeOfRelation(), 1345368106);
927 }
928
929 $result = $this->storageBackend->getMaxValueFromTable(
930 $tableName,
931 $matchFields,
932 $sortByFieldName);
933 return $result;
934 }
935
936 /**
937 * Updates a given object in the storage
938 *
939 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be updated
940 * @param array $row Row to be stored
941 * @return bool
942 */
943 protected function updateObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array $row)
944 {
945 $dataMap = $this->dataMapper->getDataMap(get_class($object));
946 $this->addCommonFieldsToRow($object, $row);
947 $row['uid'] = $object->getUid();
948 if ($dataMap->getLanguageIdColumnName() !== null) {
949 $row[$dataMap->getLanguageIdColumnName()] = (int)$object->_getProperty('_languageUid');
950 if ($object->_getProperty('_localizedUid') !== null) {
951 $row['uid'] = $object->_getProperty('_localizedUid');
952 }
953 }
954 $res = $this->storageBackend->updateRow($dataMap->getTableName(), $row);
955 if ($res === true) {
956 $this->emitAfterUpdateObjectSignal($object);
957 }
958 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
959 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
960 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $row['uid']);
961 }
962 return $res;
963 }
964
965 /**
966 * Emits a signal after an object was updated in storage
967 *
968 * @param DomainObjectInterface $object
969 */
970 protected function emitAfterUpdateObjectSignal(DomainObjectInterface $object)
971 {
972 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterUpdateObject', [$object]);
973 }
974
975 /**
976 * Emits a signal after an object was persisted
977 *
978 * @param DomainObjectInterface $object
979 */
980 protected function emitAfterPersistObjectSignal(DomainObjectInterface $object)
981 {
982 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterPersistObject', [$object]);
983 }
984
985 /**
986 * Adds common databse fields to a row
987 *
988 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
989 * @param array &$row
990 * @return void
991 */
992 protected function addCommonFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array &$row)
993 {
994 $dataMap = $this->dataMapper->getDataMap(get_class($object));
995 $this->addCommonDateFieldsToRow($object, $row);
996 if ($dataMap->getRecordTypeColumnName() !== null && $dataMap->getRecordType() !== null) {
997 $row[$dataMap->getRecordTypeColumnName()] = $dataMap->getRecordType();
998 }
999 if ($object->_isNew() && !isset($row['pid'])) {
1000 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
1001 }
1002 }
1003
1004 /**
1005 * Adjustes the common date fields of the given row to the current time
1006 *
1007 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
1008 * @param array &$row The row to be updated
1009 * @return void
1010 */
1011 protected function addCommonDateFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array &$row)
1012 {
1013 $dataMap = $this->dataMapper->getDataMap(get_class($object));
1014 if ($object->_isNew() && $dataMap->getCreationDateColumnName() !== null) {
1015 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
1016 }
1017 if ($dataMap->getModificationDateColumnName() !== null) {
1018 $row[$dataMap->getModificationDateColumnName()] = $GLOBALS['EXEC_TIME'];
1019 }
1020 }
1021
1022 /**
1023 * Iterate over deleted aggregate root objects and process them
1024 *
1025 * @return void
1026 */
1027 protected function processDeletedObjects()
1028 {
1029 foreach ($this->deletedEntities as $entity) {
1030 if ($this->session->hasObject($entity)) {
1031 $this->removeEntity($entity);
1032 $this->session->unregisterReconstitutedEntity($entity);
1033 $this->session->unregisterObject($entity);
1034 }
1035 }
1036 $this->deletedEntities = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
1037 }
1038
1039 /**
1040 * Deletes an object
1041 *
1042 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be removed from the storage
1043 * @param bool $markAsDeleted Whether to just flag the row deleted (default) or really delete it
1044 * @return void
1045 */
1046 protected function removeEntity(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, $markAsDeleted = true)
1047 {
1048 $dataMap = $this->dataMapper->getDataMap(get_class($object));
1049 $tableName = $dataMap->getTableName();
1050 if ($markAsDeleted === true && $dataMap->getDeletedFlagColumnName() !== null) {
1051 $deletedColumnName = $dataMap->getDeletedFlagColumnName();
1052 $row = [
1053 'uid' => $object->getUid(),
1054 $deletedColumnName => 1
1055 ];
1056 $this->addCommonDateFieldsToRow($object, $row);
1057 $res = $this->storageBackend->updateRow($tableName, $row);
1058 } else {
1059 $res = $this->storageBackend->removeRow($tableName, ['uid' => $object->getUid()]);
1060 }
1061 if ($res === true) {
1062 $this->emitAfterRemoveObjectSignal($object);
1063 }
1064 $this->removeRelatedObjects($object);
1065 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
1066 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
1067 $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
1068 }
1069 }
1070
1071 /**
1072 * Emits a signal after an object was removed from storage
1073 *
1074 * @param DomainObjectInterface $object
1075 */
1076 protected function emitAfterRemoveObjectSignal(DomainObjectInterface $object)
1077 {
1078 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterRemoveObject', [$object]);
1079 }
1080
1081 /**
1082 * Remove related objects
1083 *
1084 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to scanned for related objects
1085 * @return void
1086 */
1087 protected function removeRelatedObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object)
1088 {
1089 $className = get_class($object);
1090 $dataMap = $this->dataMapper->getDataMap($className);
1091 $classSchema = $this->reflectionService->getClassSchema($className);
1092 $properties = $object->_getProperties();
1093 foreach ($properties as $propertyName => $propertyValue) {
1094 $columnMap = $dataMap->getColumnMap($propertyName);
1095 if ($columnMap === null) {
1096 continue;
1097 }
1098 $propertyMetaData = $classSchema->getProperty($propertyName);
1099 if ($propertyMetaData['cascade'] === 'remove') {
1100 if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
1101 foreach ($propertyValue as $containedObject) {
1102 $this->removeEntity($containedObject);
1103 }
1104 } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
1105 $this->removeEntity($propertyValue);
1106 }
1107 } elseif ($dataMap->getDeletedFlagColumnName() === null
1108 && $columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY
1109 ) {
1110 $this->deleteAllRelationsFromRelationtable($object, $propertyName);
1111 }
1112 }
1113 }
1114
1115 /**
1116 * Determine the storage page ID for a given NEW record
1117 *
1118 * This does the following:
1119 * - If the domain object has an accessible property 'pid' (i.e. through a getPid() method), that is used to store the record.
1120 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
1121 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
1122 *
1123 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
1124 * @return int the storage Page ID where the object should be stored
1125 */
1126 protected function determineStoragePageIdForNewRecord(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object = null)
1127 {
1128 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
1129 if ($object !== null) {
1130 if (\TYPO3\CMS\Extbase\Reflection\ObjectAccess::isPropertyGettable($object, 'pid')) {
1131 $pid = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getProperty($object, 'pid');
1132 if (isset($pid)) {
1133 return (int)$pid;
1134 }
1135 }
1136 $className = get_class($object);
1137 if (isset($frameworkConfiguration['persistence']['classes'][$className]) && !empty($frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'])) {
1138 return (int)$frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'];
1139 }
1140 }
1141 $storagePidList = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $frameworkConfiguration['persistence']['storagePid']);
1142 return (int)$storagePidList[0];
1143 }
1144 }