[BUGFIX] Return null value instead of string 'NULL'
[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 }
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 * Checks if the given object has ever been persisted.
295 *
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
298 */
299 public function isNewObject($object)
300 {
301 return $this->getIdentifierByObject($object) === null;
302 }
303
304 /**
305 * Sets the aggregate root objects
306 *
307 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects
308 */
309 public function setAggregateRootObjects(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects)
310 {
311 $this->aggregateRootObjects = $objects;
312 }
313
314 /**
315 * Sets the changed objects
316 *
317 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
318 */
319 public function setChangedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities)
320 {
321 $this->changedEntities = $entities;
322 }
323
324 /**
325 * Sets the deleted objects
326 *
327 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
328 */
329 public function setDeletedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities)
330 {
331 $this->deletedEntities = $entities;
332 }
333
334 /**
335 * Commits the current persistence session.
336 */
337 public function commit()
338 {
339 $this->persistObjects();
340 $this->processDeletedObjects();
341 }
342
343 /**
344 * Traverse and persist all aggregate roots and their object graph.
345 */
346 protected function persistObjects()
347 {
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);
353 }
354 $this->persistObject($object, null);
355 }
356 foreach ($this->changedEntities as $object) {
357 $this->persistObject($object, null);
358 }
359 }
360
361 /**
362 * Persists the given object.
363 *
364 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be inserted
365 */
366 protected function persistObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object)
367 {
368 if (isset($this->visitedDuringPersistence[$object])) {
369 return;
370 }
371 $row = [];
372 $queue = [];
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)) {
377 continue;
378 }
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();
388 }
389 foreach ($propertyValue as $containedObject) {
390 if ($containedObject instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
391 $queue[] = $containedObject;
392 }
393 }
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);
399 }
400 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
401 }
402 $queue[] = $propertyValue;
403 } elseif ($object->_isNew() || $object->_isDirty($propertyName)) {
404 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, $columnMap);
405 }
406 }
407 if (!empty($row)) {
408 $this->updateObject($object, $row);
409 $object->_memorizeCleanState();
410 }
411 $this->visitedDuringPersistence[$object] = $object->getUid();
412 foreach ($queue as $queuedObject) {
413 $this->persistObject($queuedObject);
414 }
415 $this->emitAfterPersistObjectSignal($object);
416 }
417
418 /**
419 * Checks, if the property value is lazy loaded and was not initialized
420 *
421 * @param mixed $propertyValue The property value
422 * @return bool
423 */
424 protected function propertyValueIsLazyLoaded($propertyValue)
425 {
426 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
427 return true;
428 }
429 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage) {
430 if ($propertyValue->isInitialized() === false) {
431 return true;
432 }
433 }
434 return false;
435 }
436
437 /**
438 * 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
439 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
440 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
441 *
442 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objectStorage The object storage to be persisted.
443 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object. One of the properties holds the object storage.
444 * @param string $propertyName The name of the property holding the object storage.
445 * @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.
446 */
447 protected function persistObjectStorage(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $objectStorage, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, array &$row)
448 {
449 $className = get_class($parentObject);
450 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
451 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
452 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
453 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
454 if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
455 $this->removeEntity($removedObject);
456 }
457 }
458
459 $currentUids = [];
460 $sortingPosition = 1;
461 $updateSortingOfFollowing = false;
462
463 foreach ($objectStorage as $object) {
464 /** @var DomainObjectInterface $object */
465 if (empty($currentUids)) {
466 $sortingPosition = 1;
467 } else {
468 $sortingPosition++;
469 }
470 $cleanProperty = $parentObject->_getCleanProperty($propertyName);
471 if ($object->_isNew()) {
472 $this->insertObject($object);
473 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
474 // if a new object is inserted, all objects after this need to have their sorting updated
475 $updateSortingOfFollowing = true;
476 } elseif ($cleanProperty === null || $cleanProperty->getPosition($object) === null) {
477 // if parent object is new then it doesn't have cleanProperty yet; before attaching object it's clean position is null
478 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
479 // 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
480 $updateSortingOfFollowing = true;
481 } elseif ($objectStorage->isRelationDirty($object) || $cleanProperty->getPosition($object) !== $objectStorage->getPosition($object)) {
482 $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
483 $updateSortingOfFollowing = true;
484 } elseif ($updateSortingOfFollowing) {
485 if ($sortingPosition > $objectStorage->getPosition($object)) {
486 $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
487 } else {
488 $sortingPosition = $objectStorage->getPosition($object);
489 }
490 }
491 $currentUids[] = $object->getUid();
492 }
493
494 if ($columnMap->getParentKeyFieldName() === null) {
495 $row[$columnMap->getColumnName()] = implode(',', $currentUids);
496 } else {
497 $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
498 }
499 }
500
501 /**
502 * Returns the removed objects determined by a comparison of the clean property value
503 * with the actual property value.
504 *
505 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object
506 * @param string $propertyName
507 * @return array An array of removed objects
508 */
509 protected function getRemovedChildObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, $propertyName)
510 {
511 $removedObjects = [];
512 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
513 if (is_array($cleanPropertyValue) || $cleanPropertyValue instanceof \Iterator) {
514 $propertyValue = $object->_getProperty($propertyName);
515 foreach ($cleanPropertyValue as $containedObject) {
516 if (!$propertyValue->contains($containedObject)) {
517 $removedObjects[] = $containedObject;
518 }
519 }
520 }
521 return $removedObjects;
522 }
523
524 /**
525 * Updates the fields defining the relation between the object and the parent object.
526 *
527 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
528 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
529 * @param string $parentPropertyName
530 * @param int $sortingPosition
531 */
532 protected function attachObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName, $sortingPosition = 0)
533 {
534 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
535 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
536 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
537 $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
538 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
539 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
540 }
541 }
542
543 /**
544 * Updates the fields defining the relation between the object and the parent object.
545 *
546 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
547 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject
548 * @param string $parentPropertyName
549 * @param int $sortingPosition
550 */
551 protected function updateRelationOfObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0)
552 {
553 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
554 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
555 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
556 $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
557 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
558 $this->updateRelationInRelationTable($object, $parentObject, $parentPropertyName, $sortingPosition);
559 }
560 }
561
562 /**
563 * Updates fields defining the relation between the object and the parent object in relation has-many.
564 *
565 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
566 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject
567 * @param string $parentPropertyName
568 * @param int $sortingPosition
569 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
570 */
571 protected function attachObjectToParentObjectRelationHasMany(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0)
572 {
573 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
574 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
575 if ($parentColumnMap->getTypeOfRelation() !== \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
576 throw new \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException(
577 'Parent column relation type is ' . $parentColumnMap->getTypeOfRelation() .
578 ' but should be ' . \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY,
579 1345368105
580 );
581 }
582 $row = [];
583 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
584 if ($parentKeyFieldName !== null) {
585 $row[$parentKeyFieldName] = $parentObject->getUid();
586 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
587 if ($parentTableFieldName !== null) {
588 $row[$parentTableFieldName] = $parentDataMap->getTableName();
589 }
590 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
591 if (is_array($relationTableMatchFields)) {
592 $row = array_merge($relationTableMatchFields, $row);
593 }
594 }
595 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
596 if (!empty($childSortByFieldName)) {
597 $row[$childSortByFieldName] = $sortingPosition;
598 }
599 if (!empty($row)) {
600 $this->updateObject($object, $row);
601 }
602 }
603
604 /**
605 * Updates the fields defining the relation between the object and the parent object.
606 *
607 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
608 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
609 * @param string $parentPropertyName
610 */
611 protected function detachObjectFromParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
612 {
613 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
614 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
615 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
616 $row = [];
617 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
618 if ($parentKeyFieldName !== null) {
619 $row[$parentKeyFieldName] = 0;
620 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
621 if ($parentTableFieldName !== null) {
622 $row[$parentTableFieldName] = '';
623 }
624 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
625 if (is_array($relationTableMatchFields) && !empty($relationTableMatchFields)) {
626 $row = array_merge(array_fill_keys(array_keys($relationTableMatchFields), ''), $row);
627 }
628 }
629 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
630 if (!empty($childSortByFieldName)) {
631 $row[$childSortByFieldName] = 0;
632 }
633 if (!empty($row)) {
634 $this->updateObject($object, $row);
635 }
636 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
637 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
638 }
639 }
640
641 /**
642 * Inserts an object in the storage backend
643 *
644 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be insterted in the storage
645 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parentobject.
646 * @param string $parentPropertyName
647 */
648 protected function insertObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject = null, $parentPropertyName = '')
649 {
650 if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject) {
651 $result = $this->getUidOfAlreadyPersistedValueObject($object);
652 if ($result !== false) {
653 $object->_setProperty('uid', (int)$result);
654 return;
655 }
656 }
657 $dataMap = $this->dataMapper->getDataMap(get_class($object));
658 $row = [];
659 $properties = $object->_getProperties();
660 foreach ($properties as $propertyName => $propertyValue) {
661 if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) {
662 continue;
663 }
664 $columnMap = $dataMap->getColumnMap($propertyName);
665 if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_ONE) {
666 $row[$columnMap->getColumnName()] = 0;
667 } elseif ($columnMap->getTypeOfRelation() !== ColumnMap::RELATION_NONE) {
668 if ($columnMap->getParentKeyFieldName() === null) {
669 // CSV type relation
670 $row[$columnMap->getColumnName()] = '';
671 } else {
672 // MM type relation
673 $row[$columnMap->getColumnName()] = 0;
674 }
675 } elseif ($propertyValue !== null) {
676 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, $columnMap);
677 }
678 }
679 $this->addCommonFieldsToRow($object, $row);
680 if ($dataMap->getLanguageIdColumnName() !== null && $object->_getProperty('_languageUid') === null) {
681 $row[$dataMap->getLanguageIdColumnName()] = 0;
682 $object->_setProperty('_languageUid', 0);
683 }
684 if ($dataMap->getTranslationOriginColumnName() !== null) {
685 $row[$dataMap->getTranslationOriginColumnName()] = 0;
686 }
687 if ($dataMap->getTranslationOriginDiffSourceName() !== null) {
688 $row[$dataMap->getTranslationOriginDiffSourceName()] = '';
689 }
690 if ($parentObject !== null && $parentPropertyName) {
691 $parentColumnDataMap = $this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($parentPropertyName);
692 $relationTableMatchFields = $parentColumnDataMap->getRelationTableMatchFields();
693 if (is_array($relationTableMatchFields)) {
694 $row = array_merge($relationTableMatchFields, $row);
695 }
696 if ($parentColumnDataMap->getParentKeyFieldName() !== null) {
697 $row[$parentColumnDataMap->getParentKeyFieldName()] = (int)$parentObject->getUid();
698 }
699 }
700 $uid = $this->storageBackend->addRow($dataMap->getTableName(), $row);
701 $object->_setProperty('uid', (int)$uid);
702 $object->setPid((int)$row['pid']);
703 if ((int)$uid >= 1) {
704 $this->emitAfterInsertObjectSignal($object);
705 }
706 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
707 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
708 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $uid);
709 }
710 $this->session->registerObject($object, $uid);
711 if ((int)$uid >= 1) {
712 $this->emitEndInsertObjectSignal($object);
713 }
714 }
715
716 /**
717 * Emits a signal after an object was added to the storage
718 *
719 * @param DomainObjectInterface $object
720 */
721 protected function emitAfterInsertObjectSignal(DomainObjectInterface $object)
722 {
723 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterInsertObject', [$object]);
724 }
725
726 /**
727 * Emits a signal after an object was registered in persistence session
728 * This signal replaces the afterInsertObject signal which is now deprecated
729 *
730 * @param DomainObjectInterface $object
731 */
732 protected function emitEndInsertObjectSignal(DomainObjectInterface $object)
733 {
734 $this->signalSlotDispatcher->dispatch(__CLASS__, 'endInsertObject', [$object]);
735 }
736
737 /**
738 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
739 *
740 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object The object to be tested
741 * @return mixed The matching uid if an object was found, else FALSE
742 */
743 protected function getUidOfAlreadyPersistedValueObject(\TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object)
744 {
745 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
746 }
747
748 /**
749 * Inserts mm-relation into a relation table
750 *
751 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
752 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
753 * @param string $propertyName The name of the parent object's property where the related objects are stored in
754 * @param int $sortingPosition Defaults to NULL
755 * @return int The uid of the inserted row
756 */
757 protected function insertRelationInRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, $sortingPosition = null)
758 {
759 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
760 $columnMap = $dataMap->getColumnMap($propertyName);
761 $parentUid = $parentObject->getUid();
762 if ($parentObject->_getProperty('_localizedUid') !== null) {
763 $parentUid = $parentObject->_getProperty('_localizedUid');
764 }
765 $row = [
766 $columnMap->getParentKeyFieldName() => (int)$parentUid,
767 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
768 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
769 ];
770 $relationTableName = $columnMap->getRelationTableName();
771 if ($columnMap->getRelationTablePageIdColumnName() !== null) {
772 $row[$columnMap->getRelationTablePageIdColumnName()] = $this->determineStoragePageIdForNewRecord();
773 }
774 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
775 if (is_array($relationTableMatchFields)) {
776 $row = array_merge($relationTableMatchFields, $row);
777 }
778 $relationTableInsertFields = $columnMap->getRelationTableInsertFields();
779 if (is_array($relationTableInsertFields)) {
780 $row = array_merge($relationTableInsertFields, $row);
781 }
782 $res = $this->storageBackend->addRow($relationTableName, $row, true);
783 return $res;
784 }
785
786 /**
787 * Updates mm-relation in a relation table
788 *
789 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
790 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
791 * @param string $propertyName The name of the parent object's property where the related objects are stored in
792 * @param int $sortingPosition Defaults to NULL
793 * @return bool TRUE if update was successfully
794 */
795 protected function updateRelationInRelationTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, $sortingPosition = 0)
796 {
797 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
798 $columnMap = $dataMap->getColumnMap($propertyName);
799 $row = [
800 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
801 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
802 $columnMap->getChildSortByFieldName() => (int)$sortingPosition
803 ];
804 $relationTableName = $columnMap->getRelationTableName();
805 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
806 if (is_array($relationTableMatchFields)) {
807 $row = array_merge($relationTableMatchFields, $row);
808 }
809 $res = $this->storageBackend->updateRelationTableRow(
810 $relationTableName,
811 $row
812 );
813 return $res;
814 }
815
816 /**
817 * Delete all mm-relations of a parent from a relation table
818 *
819 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
820 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
821 * @return bool TRUE if delete was successfully
822 */
823 protected function deleteAllRelationsFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
824 {
825 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
826 $columnMap = $dataMap->getColumnMap($parentPropertyName);
827 $relationTableName = $columnMap->getRelationTableName();
828 $relationMatchFields = [
829 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
830 ];
831 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
832 if (is_array($relationTableMatchFields)) {
833 $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields);
834 }
835 $res = $this->storageBackend->removeRow($relationTableName, $relationMatchFields, false);
836 return $res;
837 }
838
839 /**
840 * Delete an mm-relation from a relation table
841 *
842 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $relatedObject The related object
843 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
844 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
845 * @return bool
846 */
847 protected function deleteRelationFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $relatedObject, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
848 {
849 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
850 $columnMap = $dataMap->getColumnMap($parentPropertyName);
851 $relationTableName = $columnMap->getRelationTableName();
852 $relationMatchFields = [
853 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
854 $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid()
855 ];
856 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
857 if (is_array($relationTableMatchFields)) {
858 $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields);
859 }
860 $res = $this->storageBackend->removeRow($relationTableName, $relationMatchFields, false);
861 return $res;
862 }
863
864 /**
865 * Fetches maximal value currently used for sorting field in parent table
866 *
867 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
868 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
869 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
870 * @return mixed the max value
871 */
872 protected function fetchMaxSortingFromParentTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
873 {
874 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
875 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
876 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
877 $tableName = $parentColumnMap->getChildTableName();
878 $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
879
880 if (empty($sortByFieldName)) {
881 return false;
882 }
883 $matchFields = [];
884 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
885 if ($parentKeyFieldName !== null) {
886 $matchFields[$parentKeyFieldName] = $parentObject->getUid();
887 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
888 if ($parentTableFieldName !== null) {
889 $matchFields[$parentTableFieldName] = $parentDataMap->getTableName();
890 }
891 }
892
893 if (empty($matchFields)) {
894 return false;
895 }
896 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
897 $tableName = $parentColumnMap->getRelationTableName();
898 $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
899
900 $matchFields = [
901 $parentColumnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
902 ];
903
904 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
905 if (is_array($relationTableMatchFields)) {
906 $matchFields = array_merge($relationTableMatchFields, $matchFields);
907 }
908 } else {
909 throw new \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException('Unexpected parent column relation type: ' . $parentColumnMap->getTypeOfRelation(), 1345368106);
910 }
911
912 $result = $this->storageBackend->getMaxValueFromTable(
913 $tableName,
914 $matchFields,
915 $sortByFieldName
916 );
917 return $result;
918 }
919
920 /**
921 * Updates a given object in the storage
922 *
923 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be updated
924 * @param array $row Row to be stored
925 * @return bool
926 */
927 protected function updateObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array $row)
928 {
929 $dataMap = $this->dataMapper->getDataMap(get_class($object));
930 $this->addCommonFieldsToRow($object, $row);
931 $row['uid'] = $object->getUid();
932 if ($dataMap->getLanguageIdColumnName() !== null) {
933 $row[$dataMap->getLanguageIdColumnName()] = (int)$object->_getProperty('_languageUid');
934 if ($object->_getProperty('_localizedUid') !== null) {
935 $row['uid'] = $object->_getProperty('_localizedUid');
936 }
937 }
938 $res = $this->storageBackend->updateRow($dataMap->getTableName(), $row);
939 if ($res === true) {
940 $this->emitAfterUpdateObjectSignal($object);
941 }
942 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
943 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
944 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $row['uid']);
945 }
946 return $res;
947 }
948
949 /**
950 * Emits a signal after an object was updated in storage
951 *
952 * @param DomainObjectInterface $object
953 */
954 protected function emitAfterUpdateObjectSignal(DomainObjectInterface $object)
955 {
956 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterUpdateObject', [$object]);
957 }
958
959 /**
960 * Emits a signal after an object was persisted
961 *
962 * @param DomainObjectInterface $object
963 */
964 protected function emitAfterPersistObjectSignal(DomainObjectInterface $object)
965 {
966 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterPersistObject', [$object]);
967 }
968
969 /**
970 * Adds common databse fields to a row
971 *
972 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
973 * @param array &$row
974 */
975 protected function addCommonFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array &$row)
976 {
977 $dataMap = $this->dataMapper->getDataMap(get_class($object));
978 $this->addCommonDateFieldsToRow($object, $row);
979 if ($dataMap->getRecordTypeColumnName() !== null && $dataMap->getRecordType() !== null) {
980 $row[$dataMap->getRecordTypeColumnName()] = $dataMap->getRecordType();
981 }
982 if ($object->_isNew() && !isset($row['pid'])) {
983 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
984 }
985 }
986
987 /**
988 * Adjustes the common date fields of the given row to the current time
989 *
990 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
991 * @param array &$row The row to be updated
992 */
993 protected function addCommonDateFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array &$row)
994 {
995 $dataMap = $this->dataMapper->getDataMap(get_class($object));
996 if ($object->_isNew() && $dataMap->getCreationDateColumnName() !== null) {
997 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
998 }
999 if ($dataMap->getModificationDateColumnName() !== null) {
1000 $row[$dataMap->getModificationDateColumnName()] = $GLOBALS['EXEC_TIME'];
1001 }
1002 }
1003
1004 /**
1005 * Iterate over deleted aggregate root objects and process them
1006 */
1007 protected function processDeletedObjects()
1008 {
1009 foreach ($this->deletedEntities as $entity) {
1010 if ($this->session->hasObject($entity)) {
1011 $this->removeEntity($entity);
1012 $this->session->unregisterReconstitutedEntity($entity);
1013 $this->session->unregisterObject($entity);
1014 }
1015 }
1016 $this->deletedEntities = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
1017 }
1018
1019 /**
1020 * Deletes an object
1021 *
1022 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be removed from the storage
1023 * @param bool $markAsDeleted Whether to just flag the row deleted (default) or really delete it
1024 */
1025 protected function removeEntity(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, $markAsDeleted = true)
1026 {
1027 $dataMap = $this->dataMapper->getDataMap(get_class($object));
1028 $tableName = $dataMap->getTableName();
1029 if ($markAsDeleted === true && $dataMap->getDeletedFlagColumnName() !== null) {
1030 $deletedColumnName = $dataMap->getDeletedFlagColumnName();
1031 $row = [
1032 'uid' => $object->getUid(),
1033 $deletedColumnName => 1
1034 ];
1035 $this->addCommonDateFieldsToRow($object, $row);
1036 $res = $this->storageBackend->updateRow($tableName, $row);
1037 } else {
1038 $res = $this->storageBackend->removeRow($tableName, ['uid' => $object->getUid()]);
1039 }
1040 if ($res === true) {
1041 $this->emitAfterRemoveObjectSignal($object);
1042 }
1043 $this->removeRelatedObjects($object);
1044 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
1045 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
1046 $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
1047 }
1048 }
1049
1050 /**
1051 * Emits a signal after an object was removed from storage
1052 *
1053 * @param DomainObjectInterface $object
1054 */
1055 protected function emitAfterRemoveObjectSignal(DomainObjectInterface $object)
1056 {
1057 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterRemoveObject', [$object]);
1058 }
1059
1060 /**
1061 * Remove related objects
1062 *
1063 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to scanned for related objects
1064 */
1065 protected function removeRelatedObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object)
1066 {
1067 $className = get_class($object);
1068 $dataMap = $this->dataMapper->getDataMap($className);
1069 $classSchema = $this->reflectionService->getClassSchema($className);
1070 $properties = $object->_getProperties();
1071 foreach ($properties as $propertyName => $propertyValue) {
1072 $columnMap = $dataMap->getColumnMap($propertyName);
1073 if ($columnMap === null) {
1074 continue;
1075 }
1076 $propertyMetaData = $classSchema->getProperty($propertyName);
1077 if ($propertyMetaData['cascade'] === 'remove') {
1078 if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
1079 foreach ($propertyValue as $containedObject) {
1080 $this->removeEntity($containedObject);
1081 }
1082 } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
1083 $this->removeEntity($propertyValue);
1084 }
1085 } elseif ($dataMap->getDeletedFlagColumnName() === null
1086 && $columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY
1087 ) {
1088 $this->deleteAllRelationsFromRelationtable($object, $propertyName);
1089 }
1090 }
1091 }
1092
1093 /**
1094 * Determine the storage page ID for a given NEW record
1095 *
1096 * This does the following:
1097 * - If the domain object has an accessible property 'pid' (i.e. through a getPid() method), that is used to store the record.
1098 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
1099 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
1100 *
1101 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
1102 * @return int the storage Page ID where the object should be stored
1103 */
1104 protected function determineStoragePageIdForNewRecord(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object = null)
1105 {
1106 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
1107 if ($object !== null) {
1108 if (\TYPO3\CMS\Extbase\Reflection\ObjectAccess::isPropertyGettable($object, 'pid')) {
1109 $pid = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getProperty($object, 'pid');
1110 if (isset($pid)) {
1111 return (int)$pid;
1112 }
1113 }
1114 $className = get_class($object);
1115 if (isset($frameworkConfiguration['persistence']['classes'][$className]) && !empty($frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'])) {
1116 return (int)$frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'];
1117 }
1118 }
1119 $storagePidList = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $frameworkConfiguration['persistence']['storagePid']);
1120 return (int)$storagePidList[0];
1121 }
1122
1123 /**
1124 * Returns a plain value
1125 *
1126 * i.e. objects are flattened out if possible.
1127 * Checks explicitly for null values as DataMapper's getPlainValue would convert this to 'NULL'
1128 *
1129 * @param mixed $input The value that will be converted
1130 * @param ColumnMap $columnMap Optional column map for retrieving the date storage format
1131 * @return int|string|null
1132 */
1133 protected function getPlainValue($input, ColumnMap $columnMap = null)
1134 {
1135 return $input !== null
1136 ? $this->dataMapper->getPlainValue($input, $columnMap)
1137 : null;
1138 }
1139 }