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