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