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