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