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