cdc43022bab1fee76d157e89cbc3cff75741a5f3
[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 if ($this->session->hasObject($object)) {
209 return $this->session->getIdentifierByObject($object);
210 } else {
211 return NULL;
212 }
213 }
214
215 /**
216 * Returns the object with the (internal) identifier, if it is known to the
217 * backend. Otherwise NULL is returned.
218 *
219 * @param string $identifier
220 * @param string $className
221 * @return object The object for the identifier if it is known, or NULL
222 */
223 public function getObjectByIdentifier($identifier, $className) {
224 if ($this->session->hasIdentifier($identifier, $className)) {
225 return $this->session->getObjectByIdentifier($identifier, $className);
226 } else {
227 $query = $this->persistenceManager->createQueryForType($className);
228 $query->getQuerySettings()->setRespectStoragePage(FALSE);
229 $query->getQuerySettings()->setRespectSysLanguage(FALSE);
230 return $query->matching($query->equals('uid', $identifier))->execute()->getFirst();
231 }
232 }
233
234 /**
235 * Checks if the given object has ever been persisted.
236 *
237 * @param object $object The object to check
238 * @return boolean TRUE if the object is new, FALSE if the object exists in the repository
239 */
240 public function isNewObject($object) {
241 return $this->getIdentifierByObject($object) === NULL;
242 }
243
244 /**
245 * Replaces the given object by the second object.
246 *
247 * This method will unregister the existing object at the identity map and
248 * register the new object instead. The existing object must therefore
249 * already be registered at the identity map which is the case for all
250 * reconstituted objects.
251 *
252 * The new object will be identified by the uid which formerly belonged
253 * to the existing object. The existing object looses its uid.
254 *
255 * @param object $existingObject The existing object
256 * @param object $newObject The new object
257 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\UnknownObjectException
258 * @return void
259 * @deprecated since 6.1, will be removed two versions later
260 */
261 public function replaceObject($existingObject, $newObject) {
262 $this->session->replaceReconstitutedEntity($existingObject, $newObject);
263 }
264
265 /**
266 * Sets the aggregate root objects
267 *
268 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects
269 * @return void
270 */
271 public function setAggregateRootObjects(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects) {
272 $this->aggregateRootObjects = $objects;
273 }
274
275 /**
276 * Sets the changed objects
277 *
278 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
279 * @return void
280 */
281 public function setChangedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities) {
282 $this->changedEntities = $entities;
283 }
284
285 /**
286 * Sets the deleted objects
287 *
288 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
289 * @return void
290 */
291 public function setDeletedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities) {
292 $this->deletedEntities = $entities;
293 }
294
295 /**
296 * Commits the current persistence session.
297 *
298 * @return void
299 */
300 public function commit() {
301 $this->persistObjects();
302 $this->processDeletedObjects();
303 }
304
305 /**
306 * Sets the deleted objects
307 *
308 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects
309 * @return void
310 * @deprecated since 6.1, will be removed two versions later
311 */
312 public function setDeletedObjects(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects) {
313 $this->setDeletedEntities($objects);
314 }
315
316 /**
317 * Traverse and persist all aggregate roots and their object graph.
318 *
319 * @return void
320 */
321 protected function persistObjects() {
322 $this->visitedDuringPersistence = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
323 foreach ($this->aggregateRootObjects as $object) {
324 if ($object->_isNew()) {
325 $this->insertObject($object);
326 }
327 $this->persistObject($object, NULL);
328 }
329 foreach ($this->changedEntities as $object) {
330 $this->persistObject($object, NULL);
331 }
332 }
333
334 /**
335 * Persists the given object.
336 *
337 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be inserted
338 * @return void
339 */
340 protected function persistObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object) {
341 if (isset($this->visitedDuringPersistence[$object])) {
342 return;
343 }
344 $row = array();
345 $queue = array();
346 $dataMap = $this->dataMapper->getDataMap(get_class($object));
347 $properties = $object->_getProperties();
348 foreach ($properties as $propertyName => $propertyValue) {
349 if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) {
350 continue;
351 }
352 $columnMap = $dataMap->getColumnMap($propertyName);
353 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage) {
354 $cleanProperty = $object->_getCleanProperty($propertyName);
355 // objectstorage needs to be persisted if the object is new, the objectstorge is dirty, meaning it has
356 // been changed after initial build, or a empty objectstorge is present and the cleanstate objectstorage
357 // has childelements, meaning all elements should been removed from the objectstorage
358 if ($object->_isNew() || $propertyValue->_isDirty() || ($propertyValue->count() == 0 && $cleanProperty && $cleanProperty->count() > 0)) {
359 $this->persistObjectStorage($propertyValue, $object, $propertyName, $row);
360 $propertyValue->_memorizeCleanState();
361 }
362 foreach ($propertyValue as $containedObject) {
363 if ($containedObject instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
364 $queue[] = $containedObject;
365 }
366 }
367 } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
368 if ($object->_isDirty($propertyName)) {
369 if ($propertyValue->_isNew()) {
370 $this->insertObject($propertyValue);
371 }
372 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
373 }
374 $queue[] = $propertyValue;
375 } elseif ($object->_isNew() || $object->_isDirty($propertyName)) {
376 $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, $columnMap);
377 }
378 }
379 if (count($row) > 0) {
380 $this->updateObject($object, $row);
381 $object->_memorizeCleanState();
382 }
383 $this->visitedDuringPersistence[$object] = $object->getUid();
384 foreach ($queue as $queuedObject) {
385 $this->persistObject($queuedObject);
386 }
387 }
388
389 /**
390 * Checks, if the property value is lazy loaded and was not initialized
391 *
392 * @param mixed $propertyValue The property value
393 * @return boolean
394 */
395 protected function propertyValueIsLazyLoaded($propertyValue) {
396 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
397 return TRUE;
398 }
399 if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage) {
400 if ($propertyValue->isInitialized() === FALSE) {
401 return TRUE;
402 }
403 }
404 return FALSE;
405 }
406
407 /**
408 * 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
409 * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
410 * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
411 *
412 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objectStorage The object storage to be persisted.
413 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object. One of the properties holds the object storage.
414 * @param string $propertyName The name of the property holding the object storage.
415 * @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.
416 * @return void
417 */
418 protected function persistObjectStorage(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $objectStorage, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, array &$row) {
419 $className = get_class($parentObject);
420 $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
421 $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
422 foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
423 $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
424 if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY && $propertyMetaData['cascade'] === 'remove') {
425 $this->removeEntity($removedObject);
426 }
427 }
428
429 $currentUids = array();
430 $sortingPosition = 1;
431 $updateSortingOfFollowing = FALSE;
432
433 foreach ($objectStorage as $object) {
434 if (empty($currentUids)) {
435 $sortingPosition = 1;
436 } else {
437 $sortingPosition++;
438 }
439 $cleanProperty = $parentObject->_getCleanProperty($propertyName);
440 if ($object->_isNew()) {
441 $this->insertObject($object);
442 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
443 // if a new object is inserted, all objects after this need to have their sorting updated
444 $updateSortingOfFollowing = TRUE;
445 } elseif ($cleanProperty === NULL || $cleanProperty->getPosition($object) === NULL) {
446 // if parent object is new then it doesn't have cleanProperty yet; before attaching object it's clean position is null
447 $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
448 // 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
449 $updateSortingOfFollowing = TRUE;
450 } elseif ($objectStorage->isRelationDirty($object) || $cleanProperty->getPosition($object) !== $objectStorage->getPosition($object)) {
451 $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
452 $updateSortingOfFollowing = TRUE;
453 } elseif ($updateSortingOfFollowing) {
454 if ($sortingPosition > $objectStorage->getPosition($object)) {
455 $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
456 } else {
457 $sortingPosition = $objectStorage->getPosition($object);
458 }
459 }
460 $currentUids[] = $object->getUid();
461 }
462
463 if ($columnMap->getParentKeyFieldName() === NULL) {
464 $row[$columnMap->getColumnName()] = implode(',', $currentUids);
465 } else {
466 $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
467 }
468 }
469
470 /**
471 * Returns the removed objects determined by a comparison of the clean property value
472 * with the actual property value.
473 *
474 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object
475 * @param string $propertyName
476 * @return array An array of removed objects
477 */
478 protected function getRemovedChildObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, $propertyName) {
479 $removedObjects = array();
480 $cleanPropertyValue = $object->_getCleanProperty($propertyName);
481 if (is_array($cleanPropertyValue) || $cleanPropertyValue instanceof \Iterator) {
482 $propertyValue = $object->_getProperty($propertyName);
483 foreach ($cleanPropertyValue as $containedObject) {
484 if (!$propertyValue->contains($containedObject)) {
485 $removedObjects[] = $containedObject;
486 }
487 }
488 }
489 return $removedObjects;
490 }
491
492 /**
493 * Updates the fields defining the relation between the object and the parent object.
494 *
495 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
496 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
497 * @param string $parentPropertyName
498 * @param integer $sortingPosition
499 * @return void
500 */
501 protected function attachObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName, $sortingPosition = 0) {
502 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
503 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
504 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
505 $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
506 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
507 $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
508 }
509 }
510
511 /**
512 * Updates the fields defining the relation between the object and the parent object.
513 *
514 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
515 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
516 * @param string $parentPropertyName
517 * @param integer $sortingPosition
518 * @return void
519 */
520 protected function updateRelationOfObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0) {
521 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
522 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
523 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
524 $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
525 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
526 $this->updateRelationInRelationTable($object, $parentObject, $parentPropertyName, $sortingPosition);
527 }
528 }
529
530 /**
531 * Updates fields defining the relation between the object and the parent object in relation has-many.
532 *
533 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
534 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject
535 * @param string $parentPropertyName
536 * @param integer $sortingPosition
537 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
538 * @return void
539 */
540 protected function attachObjectToParentObjectRelationHasMany(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0) {
541 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
542 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
543 if ($parentColumnMap->getTypeOfRelation() !== \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
544 throw new \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException(
545 'Parent column relation type is ' . $parentColumnMap->getTypeOfRelation() .
546 ' but should be ' . \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY,
547 1345368105
548 );
549 }
550 $row = array();
551 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
552 if ($parentKeyFieldName !== NULL) {
553 $row[$parentKeyFieldName] = $parentObject->getUid();
554 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
555 if ($parentTableFieldName !== NULL) {
556 $row[$parentTableFieldName] = $parentDataMap->getTableName();
557 }
558 }
559 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
560 if (!empty($childSortByFieldName)) {
561 $row[$childSortByFieldName] = $sortingPosition;
562 }
563 if (!empty($row)) {
564 $this->updateObject($object, $row);
565 }
566 }
567
568 /**
569 * Updates the fields defining the relation between the object and the parent object.
570 *
571 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
572 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
573 * @param string $parentPropertyName
574 * @return void
575 */
576 protected function detachObjectFromParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName) {
577 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
578 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
579 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
580 $row = array();
581 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
582 if ($parentKeyFieldName !== NULL) {
583 $row[$parentKeyFieldName] = '';
584 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
585 if ($parentTableFieldName !== NULL) {
586 $row[$parentTableFieldName] = '';
587 }
588 }
589 $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
590 if (!empty($childSortByFieldName)) {
591 $row[$childSortByFieldName] = 0;
592 }
593 if (count($row) > 0) {
594 $this->updateObject($object, $row);
595 }
596 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
597 $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
598 }
599 }
600
601 /**
602 * Inserts an object in the storage backend
603 *
604 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be insterted in the storage
605 * @return void
606 */
607 protected function insertObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object) {
608 if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject) {
609 $result = $this->getUidOfAlreadyPersistedValueObject($object);
610 if ($result !== FALSE) {
611 $object->_setProperty('uid', (integer) $result);
612 return;
613 }
614 }
615 $dataMap = $this->dataMapper->getDataMap(get_class($object));
616 $row = array();
617 $this->addCommonFieldsToRow($object, $row);
618 if ($dataMap->getLanguageIdColumnName() !== NULL) {
619 $row[$dataMap->getLanguageIdColumnName()] = -1;
620 }
621 $uid = $this->storageBackend->addRow($dataMap->getTableName(), $row);
622 $object->_setProperty('uid', (integer) $uid);
623 if ((integer) $uid >= 1) {
624 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterInsertObject', array('object' => $object));
625 }
626 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
627 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
628 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $uid);
629 }
630 $this->session->registerObject($object, $uid);
631 }
632
633 /**
634 * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
635 *
636 * @param \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object The object to be tested
637 * @return mixed The matching uid if an object was found, else FALSE
638 */
639 protected function getUidOfAlreadyPersistedValueObject(\TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object) {
640 return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
641 }
642
643 /**
644 * Inserts mm-relation into a relation table
645 *
646 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
647 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
648 * @param string $propertyName The name of the parent object's property where the related objects are stored in
649 * @param integer $sortingPosition Defaults to NULL
650 * @return integer The uid of the inserted row
651 */
652 protected function insertRelationInRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, $sortingPosition = NULL) {
653 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
654 $columnMap = $dataMap->getColumnMap($propertyName);
655 $row = array(
656 $columnMap->getParentKeyFieldName() => (integer) $parentObject->getUid(),
657 $columnMap->getChildKeyFieldName() => (integer) $object->getUid(),
658 $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (integer) $sortingPosition : 0
659 );
660 $relationTableName = $columnMap->getRelationTableName();
661 // FIXME Reenable support for tablenames
662 // $childTableName = $columnMap->getChildTableName();
663 // if (isset($childTableName)) {
664 // $row['tablenames'] = $childTableName;
665 // }
666 if ($columnMap->getRelationTablePageIdColumnName() !== NULL) {
667 $row[$columnMap->getRelationTablePageIdColumnName()] = $this->determineStoragePageIdForNewRecord();
668 }
669 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
670 if (count($relationTableMatchFields)) {
671 foreach ($relationTableMatchFields as $matchField => $matchValue) {
672 $row[$matchField] = $matchValue;
673 }
674 }
675 $relationTableInsertFields = $columnMap->getRelationTableInsertFields();
676 if (count($relationTableInsertFields)) {
677 foreach ($relationTableInsertFields as $insertField => $insertValue) {
678 $row[$insertField] = $insertValue;
679 }
680 }
681 $res = $this->storageBackend->addRow($relationTableName, $row, TRUE);
682 return $res;
683 }
684
685 /**
686 * Inserts mm-relation into a relation table
687 *
688 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
689 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
690 * @param string $propertyName The name of the parent object's property where the related objects are stored in
691 * @param integer $sortingPosition Defaults to NULL
692 * @return integer The uid of the inserted row
693 */
694 protected function updateRelationInRelationTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, $sortingPosition = 0) {
695 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
696 $columnMap = $dataMap->getColumnMap($propertyName);
697 $row = array(
698 $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
699 $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
700 $columnMap->getChildSortByFieldName() => (int)$sortingPosition
701 );
702 $relationTableName = $columnMap->getRelationTableName();
703 // FIXME Reenable support for tablenames
704 // $childTableName = $columnMap->getChildTableName();
705 // if (isset($childTableName)) {
706 // $row['tablenames'] = $childTableName;
707 // }
708
709 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
710 if (is_array($relationTableMatchFields) && count($relationTableMatchFields) > 0) {
711 $row = array_merge($relationTableMatchFields, $row);
712 }
713 $res = $this->storageBackend->updateRelationTableRow(
714 $relationTableName,
715 $row);
716 return $res;
717 }
718
719 /**
720 * Delete all mm-relations of a parent from a relation table
721 *
722 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
723 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
724 * @return boolean
725 */
726 protected function deleteAllRelationsFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName) {
727 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
728 $columnMap = $dataMap->getColumnMap($parentPropertyName);
729 $relationTableName = $columnMap->getRelationTableName();
730 $relationMatchFields = array(
731 $columnMap->getParentKeyFieldName() => (integer) $parentObject->getUid()
732 );
733 $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
734 if (is_array($relationTableMatchFields) && count($relationTableMatchFields) > 0) {
735 $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields);
736 }
737 $res = $this->storageBackend->removeRow($relationTableName, $relationMatchFields, FALSE);
738 return $res;
739 }
740
741 /**
742 * Delete an mm-relation from a relation table
743 *
744 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $relatedObject The related object
745 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
746 * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
747 * @return boolean
748 */
749 protected function deleteRelationFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $relatedObject, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName) {
750 $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
751 $columnMap = $dataMap->getColumnMap($parentPropertyName);
752 $relationTableName = $columnMap->getRelationTableName();
753 $res = $this->storageBackend->removeRow($relationTableName, array(
754 $columnMap->getParentKeyFieldName() => (integer) $parentObject->getUid(),
755 $columnMap->getChildKeyFieldName() => (integer) $relatedObject->getUid()
756 ), FALSE);
757 return $res;
758 }
759
760 /**
761 * Fetches maximal value currently used for sorting field in parent table
762 *
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 * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
766 * @return mixed the max value
767 */
768 protected function fetchMaxSortingFromParentTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName) {
769 $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
770 $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
771 if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
772 $tableName = $parentColumnMap->getChildTableName();
773 $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
774
775 if (empty($sortByFieldName)) {
776 return FALSE;
777 }
778 $matchFields = array();
779 $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
780 if ($parentKeyFieldName !== NULL) {
781 $matchFields[$parentKeyFieldName] = $parentObject->getUid();
782 $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
783 if ($parentTableFieldName !== NULL) {
784 $matchFields[$parentTableFieldName] = $parentDataMap->getTableName();
785 }
786 }
787
788 if (empty($matchFields)) {
789 return FALSE;
790 }
791 } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
792 $tableName = $parentColumnMap->getRelationTableName();
793 $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
794
795 $matchFields = array(
796 $parentColumnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
797 );
798
799 $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
800 if (is_array($relationTableMatchFields) && count($relationTableMatchFields) > 0) {
801 $matchFields = array_merge($relationTableMatchFields, $matchFields);
802 }
803 } else {
804 throw new \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException('Unexpected parent column relation type:' . $parentColumnMap->getTypeOfRelation(), 1345368106);
805 }
806
807 $result = $this->storageBackend->getMaxValueFromTable(
808 $tableName,
809 $matchFields,
810 $sortByFieldName);
811 return $result;
812 }
813
814 /**
815 * Updates a given object in the storage
816 *
817 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be updated
818 * @param array $row Row to be stored
819 * @return boolean
820 */
821 protected function updateObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array $row) {
822 $dataMap = $this->dataMapper->getDataMap(get_class($object));
823 $this->addCommonFieldsToRow($object, $row);
824 $row['uid'] = $object->getUid();
825 if ($dataMap->getLanguageIdColumnName() !== NULL) {
826 $row[$dataMap->getLanguageIdColumnName()] = $object->_getProperty('_languageUid');
827 if ($object->_getProperty('_localizedUid') !== NULL) {
828 $row['uid'] = $object->_getProperty('_localizedUid');
829 }
830 }
831 $res = $this->storageBackend->updateRow($dataMap->getTableName(), $row);
832 if ($res === TRUE) {
833 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterUpdateObject', array('object' => $object));
834 }
835 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
836 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
837 $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $row['uid']);
838 }
839 return $res;
840 }
841
842 /**
843 * Adds common databse fields to a row
844 *
845 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
846 * @param array &$row
847 * @return void
848 */
849 protected function addCommonFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array &$row) {
850 $dataMap = $this->dataMapper->getDataMap(get_class($object));
851 $this->addCommonDateFieldsToRow($object, $row);
852 if ($dataMap->getRecordTypeColumnName() !== NULL && $dataMap->getRecordType() !== NULL) {
853 $row[$dataMap->getRecordTypeColumnName()] = $dataMap->getRecordType();
854 }
855 if ($object->_isNew() && !isset($row['pid'])) {
856 $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
857 }
858 }
859
860 /**
861 * Adjustes the common date fields of the given row to the current time
862 *
863 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
864 * @param array &$row The row to be updated
865 * @return void
866 */
867 protected function addCommonDateFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array &$row) {
868 $dataMap = $this->dataMapper->getDataMap(get_class($object));
869 if ($object->_isNew() && $dataMap->getCreationDateColumnName() !== NULL) {
870 $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
871 }
872 if ($dataMap->getModificationDateColumnName() !== NULL) {
873 $row[$dataMap->getModificationDateColumnName()] = $GLOBALS['EXEC_TIME'];
874 }
875 }
876
877 /**
878 * Iterate over deleted aggregate root objects and process them
879 *
880 * @return void
881 */
882 protected function processDeletedObjects() {
883 foreach ($this->deletedEntities as $entity) {
884 if ($this->session->hasObject($entity)) {
885 $this->removeEntity($entity);
886 $this->session->unregisterReconstitutedEntity($entity);
887 $this->session->unregisterObject($entity);
888 }
889 }
890 $this->deletedEntities = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
891 }
892
893 /**
894 * Deletes an object
895 *
896 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be removed from the storage
897 * @param boolean $markAsDeleted Wether to just flag the row deleted (default) or really delete it
898 * @return void
899 */
900 protected function removeEntity(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, $markAsDeleted = TRUE) {
901 $dataMap = $this->dataMapper->getDataMap(get_class($object));
902 $tableName = $dataMap->getTableName();
903 if ($markAsDeleted === TRUE && $dataMap->getDeletedFlagColumnName() !== NULL) {
904 $deletedColumnName = $dataMap->getDeletedFlagColumnName();
905 $row = array(
906 'uid' => $object->getUid(),
907 $deletedColumnName => 1
908 );
909 $this->addCommonDateFieldsToRow($object, $row);
910 $res = $this->storageBackend->updateRow($tableName, $row);
911 } else {
912 $res = $this->storageBackend->removeRow($tableName, array('uid' => $object->getUid()));
913 }
914 if ($res === TRUE) {
915 $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterRemoveObject', array('object' => $object));
916 }
917 $this->removeRelatedObjects($object);
918 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
919 if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
920 $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
921 }
922 }
923
924 /**
925 * Remove related objects
926 *
927 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to scanned for related objects
928 * @return void
929 */
930 protected function removeRelatedObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object) {
931 $className = get_class($object);
932 $dataMap = $this->dataMapper->getDataMap($className);
933 $classSchema = $this->reflectionService->getClassSchema($className);
934 $properties = $object->_getProperties();
935 foreach ($properties as $propertyName => $propertyValue) {
936 $columnMap = $dataMap->getColumnMap($propertyName);
937 $propertyMetaData = $classSchema->getProperty($propertyName);
938 if ($propertyMetaData['cascade'] === 'remove') {
939 if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
940 foreach ($propertyValue as $containedObject) {
941 $this->removeEntity($containedObject);
942 }
943 } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
944 $this->removeEntity($propertyValue);
945 }
946 }
947 }
948 }
949
950 /**
951 * Determine the storage page ID for a given NEW record
952 *
953 * This does the following:
954 * - If the domain object has an accessible property 'pid' (i.e. through a getPid() method), that is used to store the record.
955 * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
956 * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
957 *
958 * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
959 * @return integer the storage Page ID where the object should be stored
960 */
961 protected function determineStoragePageIdForNewRecord(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object = NULL) {
962 $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
963 if ($object !== NULL) {
964 if (\TYPO3\CMS\Extbase\Reflection\ObjectAccess::isPropertyGettable($object, 'pid')) {
965 $pid = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getProperty($object, 'pid');
966 if (isset($pid)) {
967 return (integer) $pid;
968 }
969 }
970 $className = get_class($object);
971 if (isset($frameworkConfiguration['persistence']['classes'][$className]) && !empty($frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'])) {
972 return (integer) $frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'];
973 }
974 }
975 $storagePidList = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $frameworkConfiguration['persistence']['storagePid']);
976 return (integer) $storagePidList[0];
977 }
978
979 /**
980 * Returns a plain value, i.e. objects are flattened out if possible.
981 *
982 * @param mixed $input
983 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap $columnMap
984 * @return mixed
985 */
986 protected function getPlainValue($input, $columnMap = NULL) {
987 if ($input instanceof \DateTime) {
988 if (!is_null($columnMap) && !is_null($columnMap->getDateTimeStorageFormat())) {
989 if ($columnMap->getDateTimeStorageFormat() == 'datetime') {
990 return $input->format('Y-m-d H:i:s');
991 }
992 if ($columnMap->getDateTimeStorageFormat() == 'date') {
993 return $input->format('Y-m-d');
994 }
995 } else {
996 return $input->format('U');
997 }
998 } elseif ($input instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
999 return $input->getUid();
1000 } elseif (TypeHandlingUtility::isCoreType($input)) {
1001 return (string) $input;
1002 } elseif (is_bool($input)) {
1003 return $input === TRUE ? 1 : 0;
1004 } else {
1005 return $input;
1006 }
1007 }
1008 }