Passed
Branch master (6c65a4)
by Christian
27:15 queued 11:09
created

Backend::persistObject()   C

Complexity

Conditions 21
Paths 37

Size

Total Lines 50
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 21
eloc 34
nc 37
nop 1
dl 0
loc 50
rs 5.3096
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace TYPO3\CMS\Extbase\Persistence\Generic;
3
4
/*
5
 * This file is part of the TYPO3 CMS project.
6
 *
7
 * It is free software; you can redistribute it and/or modify it under
8
 * the terms of the GNU General Public License, either version 2
9
 * of the License, or any later version.
10
 *
11
 * For the full copyright and license information, please read the
12
 * LICENSE.txt file that was distributed with this source code.
13
 *
14
 * The TYPO3 project - inspiring people to share!
15
 */
16
17
use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface;
18
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap;
19
use TYPO3\CMS\Extbase\Persistence\ObjectMonitoringInterface;
20
21
/**
22
 * A persistence backend. This backend maps objects to the relational model of the storage backend.
23
 * It persists all added, removed and changed objects.
24
 */
25
class Backend implements \TYPO3\CMS\Extbase\Persistence\Generic\BackendInterface, \TYPO3\CMS\Core\SingletonInterface
26
{
27
    /**
28
     * @var \TYPO3\CMS\Extbase\Persistence\Generic\Session
29
     */
30
    protected $session;
31
32
    /**
33
     * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface
34
     */
35
    protected $persistenceManager;
36
37
    /**
38
     * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
39
     */
40
    protected $aggregateRootObjects;
41
42
    /**
43
     * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
44
     */
45
    protected $deletedEntities;
46
47
    /**
48
     * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
49
     */
50
    protected $changedEntities;
51
52
    /**
53
     * @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage
54
     */
55
    protected $visitedDuringPersistence;
56
57
    /**
58
     * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
59
     */
60
    protected $reflectionService;
61
62
    /**
63
     * @var \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
64
     */
65
    protected $qomFactory;
66
67
    /**
68
     * @var \TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface
69
     */
70
    protected $storageBackend;
71
72
    /**
73
     * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
74
     */
75
    protected $dataMapper;
76
77
    /**
78
     * The TYPO3 reference index object
79
     *
80
     * @var \TYPO3\CMS\Core\Database\ReferenceIndex
81
     */
82
    protected $referenceIndex;
83
84
    /**
85
     * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
86
     */
87
    protected $configurationManager;
88
89
    /**
90
     * @var \TYPO3\CMS\Extbase\SignalSlot\Dispatcher
91
     */
92
    protected $signalSlotDispatcher;
93
94
    /**
95
     * @param \TYPO3\CMS\Extbase\Persistence\Generic\Session $session
96
     */
97
    public function injectSession(\TYPO3\CMS\Extbase\Persistence\Generic\Session $session)
98
    {
99
        $this->session = $session;
100
    }
101
102
    /**
103
     * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
104
     */
105
    public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
106
    {
107
        $this->reflectionService = $reflectionService;
108
    }
109
110
    /**
111
     * @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory
112
     */
113
    public function injectQomFactory(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory $qomFactory)
114
    {
115
        $this->qomFactory = $qomFactory;
116
    }
117
118
    /**
119
     * @param \TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface $storageBackend
120
     */
121
    public function injectStorageBackend(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\BackendInterface $storageBackend)
122
    {
123
        $this->storageBackend = $storageBackend;
124
    }
125
126
    /**
127
     * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper
128
     */
129
    public function injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)
130
    {
131
        $this->dataMapper = $dataMapper;
132
    }
133
134
    /**
135
     * @param \TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher
136
     */
137
    public function injectSignalSlotDispatcher(\TYPO3\CMS\Extbase\SignalSlot\Dispatcher $signalSlotDispatcher)
138
    {
139
        $this->signalSlotDispatcher = $signalSlotDispatcher;
140
    }
141
142
    /**
143
     * Constructs the backend
144
     *
145
     * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
146
     */
147
    public function __construct(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
148
    {
149
        $this->configurationManager = $configurationManager;
150
        $this->referenceIndex = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ReferenceIndex::class);
151
        $this->referenceIndex->enableRuntimeCache();
152
        $this->aggregateRootObjects = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
153
        $this->deletedEntities = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
154
        $this->changedEntities = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
155
    }
156
157
    /**
158
     * @param \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager
159
     */
160
    public function setPersistenceManager(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface $persistenceManager)
161
    {
162
        $this->persistenceManager = $persistenceManager;
163
    }
164
165
    /**
166
     * Returns the repository session
167
     *
168
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Session
169
     */
170
    public function getSession()
171
    {
172
        return $this->session;
173
    }
174
175
    /**
176
     * Returns the Data Mapper
177
     *
178
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
179
     */
180
    public function getDataMapper()
181
    {
182
        return $this->dataMapper;
183
    }
184
185
    /**
186
     * Returns the current QOM factory
187
     *
188
     * @return \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory
189
     */
190
    public function getQomFactory()
191
    {
192
        return $this->qomFactory;
193
    }
194
195
    /**
196
     * Returns the reflection service
197
     *
198
     * @return \TYPO3\CMS\Extbase\Reflection\ReflectionService
199
     */
200
    public function getReflectionService()
201
    {
202
        return $this->reflectionService;
203
    }
204
205
    /**
206
     * Returns the number of records matching the query.
207
     *
208
     * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
209
     * @return int
210
     * @api
211
     */
212
    public function getObjectCountByQuery(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query)
213
    {
214
        return $this->storageBackend->getObjectCountByQuery($query);
215
    }
216
217
    /**
218
     * Returns the object data matching the $query.
219
     *
220
     * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
221
     * @return array
222
     * @api
223
     */
224
    public function getObjectDataByQuery(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query)
225
    {
226
        $query = $this->emitBeforeGettingObjectDataSignal($query);
227
        $result = $this->storageBackend->getObjectDataByQuery($query);
228
        $result = $this->emitAfterGettingObjectDataSignal($query, $result);
229
        return $result;
230
    }
231
232
    /**
233
     * Emits a signal before object data is fetched
234
     *
235
     * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
236
     * @return \TYPO3\CMS\Extbase\Persistence\QueryInterface Modified query
237
     */
238
    protected function emitBeforeGettingObjectDataSignal(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query)
239
    {
240
        $signalArguments = $this->signalSlotDispatcher->dispatch(__CLASS__, 'beforeGettingObjectData', [$query]);
241
        return $signalArguments[0];
242
    }
243
244
    /**
245
     * Emits a signal after object data is fetched
246
     *
247
     * @param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
248
     * @param array $result
249
     * @return array Modified result
250
     */
251
    protected function emitAfterGettingObjectDataSignal(\TYPO3\CMS\Extbase\Persistence\QueryInterface $query, array $result)
252
    {
253
        $signalArguments = $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterGettingObjectData', [$query, $result]);
254
        return $signalArguments[1];
255
    }
256
257
    /**
258
     * Returns the (internal) identifier for the object, if it is known to the
259
     * backend. Otherwise NULL is returned.
260
     *
261
     * @param object $object
262
     * @return string|null The identifier for the object if it is known, or NULL
263
     */
264
    public function getIdentifierByObject($object)
265
    {
266
        if ($object instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
267
            $object = $object->_loadRealInstance();
268
            if (!is_object($object)) {
269
                return null;
270
            }
271
        }
272
        return $this->session->getIdentifierByObject($object);
273
    }
274
275
    /**
276
     * Returns the object with the (internal) identifier, if it is known to the
277
     * backend. Otherwise NULL is returned.
278
     *
279
     * @param string $identifier
280
     * @param string $className
281
     * @return object|null The object for the identifier if it is known, or NULL
282
     */
283
    public function getObjectByIdentifier($identifier, $className)
284
    {
285
        if ($this->session->hasIdentifier($identifier, $className)) {
286
            return $this->session->getObjectByIdentifier($identifier, $className);
287
        }
288
        $query = $this->persistenceManager->createQueryForType($className);
289
        $query->getQuerySettings()->setRespectStoragePage(false);
290
        $query->getQuerySettings()->setRespectSysLanguage(false);
291
        return $query->matching($query->equals('uid', $identifier))->execute()->getFirst();
292
    }
293
294
    /**
295
     * Checks if the given object has ever been persisted.
296
     *
297
     * @param object $object The object to check
298
     * @return bool TRUE if the object is new, FALSE if the object exists in the repository
299
     */
300
    public function isNewObject($object)
301
    {
302
        return $this->getIdentifierByObject($object) === null;
303
    }
304
305
    /**
306
     * Sets the aggregate root objects
307
     *
308
     * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects
309
     */
310
    public function setAggregateRootObjects(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $objects)
311
    {
312
        $this->aggregateRootObjects = $objects;
313
    }
314
315
    /**
316
     * Sets the changed objects
317
     *
318
     * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
319
     */
320
    public function setChangedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities)
321
    {
322
        $this->changedEntities = $entities;
323
    }
324
325
    /**
326
     * Sets the deleted objects
327
     *
328
     * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities
329
     */
330
    public function setDeletedEntities(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $entities)
331
    {
332
        $this->deletedEntities = $entities;
333
    }
334
335
    /**
336
     * Commits the current persistence session.
337
     */
338
    public function commit()
339
    {
340
        $this->persistObjects();
341
        $this->processDeletedObjects();
342
    }
343
344
    /**
345
     * Traverse and persist all aggregate roots and their object graph.
346
     */
347
    protected function persistObjects()
348
    {
349
        $this->visitedDuringPersistence = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
350
        foreach ($this->aggregateRootObjects as $object) {
351
            /** @var DomainObjectInterface $object */
352
            if ($object->_isNew()) {
353
                $this->insertObject($object);
354
            }
355
            $this->persistObject($object, null);
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...ackend::persistObject() has too many arguments starting with null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

355
            $this->/** @scrutinizer ignore-call */ 
356
                   persistObject($object, null);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
356
        }
357
        foreach ($this->changedEntities as $object) {
358
            $this->persistObject($object, null);
359
        }
360
    }
361
362
    /**
363
     * Persists the given object.
364
     *
365
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be inserted
366
     */
367
    protected function persistObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object)
368
    {
369
        if (isset($this->visitedDuringPersistence[$object])) {
370
            return;
371
        }
372
        $row = [];
373
        $queue = [];
374
        $dataMap = $this->dataMapper->getDataMap(get_class($object));
375
        $properties = $object->_getProperties();
376
        foreach ($properties as $propertyName => $propertyValue) {
377
            if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) {
378
                continue;
379
            }
380
            $columnMap = $dataMap->getColumnMap($propertyName);
381
            if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\ObjectStorage) {
382
                $cleanProperty = $object->_getCleanProperty($propertyName);
383
                // objectstorage needs to be persisted if the object is new, the objectstorge is dirty, meaning it has
384
                // been changed after initial build, or an empty objectstorge is present and the cleanstate objectstorage
385
                // has childelements, meaning all elements should been removed from the objectstorage
386
                if ($object->_isNew() || $propertyValue->_isDirty() || ($propertyValue->count() === 0 && $cleanProperty && $cleanProperty->count() > 0)) {
387
                    $this->persistObjectStorage($propertyValue, $object, $propertyName, $row);
388
                    $propertyValue->_memorizeCleanState();
389
                }
390
                foreach ($propertyValue as $containedObject) {
391
                    if ($containedObject instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
392
                        $queue[] = $containedObject;
393
                    }
394
                }
395
            } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface
396
                && $object instanceof ObjectMonitoringInterface) {
397
                if ($object->_isDirty($propertyName)) {
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...ngInterface::_isDirty() has too many arguments starting with $propertyName. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

397
                if ($object->/** @scrutinizer ignore-call */ _isDirty($propertyName)) {

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
398
                    if ($propertyValue->_isNew()) {
399
                        $this->insertObject($propertyValue, $object, $propertyName);
400
                    }
401
                    $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue);
402
                }
403
                $queue[] = $propertyValue;
404
            } elseif ($object->_isNew() || $object->_isDirty($propertyName)) {
0 ignored issues
show
Bug introduced by
The method _isDirty() does not exist on TYPO3\CMS\Extbase\Domain...t\DomainObjectInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Extbase\Domain...t\DomainObjectInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

404
            } elseif ($object->_isNew() || $object->/** @scrutinizer ignore-call */ _isDirty($propertyName)) {
Loading history...
405
                $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, $columnMap);
406
            }
407
        }
408
        if (!empty($row)) {
409
            $this->updateObject($object, $row);
410
            $object->_memorizeCleanState();
0 ignored issues
show
Bug introduced by
The method _memorizeCleanState() does not exist on TYPO3\CMS\Extbase\Domain...t\DomainObjectInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to TYPO3\CMS\Extbase\Domain...t\DomainObjectInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

410
            $object->/** @scrutinizer ignore-call */ 
411
                     _memorizeCleanState();
Loading history...
411
        }
412
        $this->visitedDuringPersistence[$object] = $object->getUid();
413
        foreach ($queue as $queuedObject) {
414
            $this->persistObject($queuedObject);
415
        }
416
        $this->emitAfterPersistObjectSignal($object);
417
    }
418
419
    /**
420
     * Checks, if the property value is lazy loaded and was not initialized
421
     *
422
     * @param mixed $propertyValue The property value
423
     * @return bool
424
     */
425
    protected function propertyValueIsLazyLoaded($propertyValue)
426
    {
427
        if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyLoadingProxy) {
428
            return true;
429
        }
430
        if ($propertyValue instanceof \TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage) {
431
            if ($propertyValue->isInitialized() === false) {
432
                return true;
433
            }
434
        }
435
        return false;
436
    }
437
438
    /**
439
     * Persists an object storage. Objects of a 1:n or m:n relation are queued and processed with the parent object. A 1:1 relation
440
     * gets persisted immediately. Objects which were removed from the property were detached from the parent object. They will not be
441
     * deleted by default. You have to annotate the property with "@cascade remove" if you want them to be deleted as well.
442
     *
443
     * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $objectStorage The object storage to be persisted.
444
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object. One of the properties holds the object storage.
445
     * @param string $propertyName The name of the property holding the object storage.
446
     * @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.
447
     */
448
    protected function persistObjectStorage(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $objectStorage, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, array &$row)
449
    {
450
        $className = get_class($parentObject);
451
        $columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);
452
        $propertyMetaData = $this->reflectionService->getClassSchema($className)->getProperty($propertyName);
453
        foreach ($this->getRemovedChildObjects($parentObject, $propertyName) as $removedObject) {
454
            $this->detachObjectFromParentObject($removedObject, $parentObject, $propertyName);
455
            if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY && $propertyMetaData['annotations']['cascade'] === 'remove') {
456
                $this->removeEntity($removedObject);
457
            }
458
        }
459
460
        $currentUids = [];
461
        $sortingPosition = 1;
462
        $updateSortingOfFollowing = false;
463
464
        foreach ($objectStorage as $object) {
465
            /** @var DomainObjectInterface $object */
466
            if (empty($currentUids)) {
467
                $sortingPosition = 1;
468
            } else {
469
                $sortingPosition++;
470
            }
471
            $cleanProperty = $parentObject->_getCleanProperty($propertyName);
472
            if ($object->_isNew()) {
473
                $this->insertObject($object);
474
                $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
475
                // if a new object is inserted, all objects after this need to have their sorting updated
476
                $updateSortingOfFollowing = true;
477
            } elseif ($cleanProperty === null || $cleanProperty->getPosition($object) === null) {
478
                // if parent object is new then it doesn't have cleanProperty yet; before attaching object it's clean position is null
479
                $this->attachObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
480
                // 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
481
                $updateSortingOfFollowing = true;
482
            } elseif ($objectStorage->isRelationDirty($object) || $cleanProperty->getPosition($object) !== $objectStorage->getPosition($object)) {
483
                $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
484
                $updateSortingOfFollowing = true;
485
            } elseif ($updateSortingOfFollowing) {
486
                if ($sortingPosition > $objectStorage->getPosition($object)) {
487
                    $this->updateRelationOfObjectToParentObject($object, $parentObject, $propertyName, $sortingPosition);
488
                } else {
489
                    $sortingPosition = $objectStorage->getPosition($object);
490
                }
491
            }
492
            $currentUids[] = $object->getUid();
493
        }
494
495
        if ($columnMap->getParentKeyFieldName() === null) {
496
            $row[$columnMap->getColumnName()] = implode(',', $currentUids);
497
        } else {
498
            $row[$columnMap->getColumnName()] = $this->dataMapper->countRelated($parentObject, $propertyName);
499
        }
500
    }
501
502
    /**
503
     * Returns the removed objects determined by a comparison of the clean property value
504
     * with the actual property value.
505
     *
506
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object
507
     * @param string $propertyName
508
     * @return array An array of removed objects
509
     */
510
    protected function getRemovedChildObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, $propertyName)
511
    {
512
        $removedObjects = [];
513
        $cleanPropertyValue = $object->_getCleanProperty($propertyName);
514
        if (is_array($cleanPropertyValue) || $cleanPropertyValue instanceof \Iterator) {
515
            $propertyValue = $object->_getProperty($propertyName);
516
            foreach ($cleanPropertyValue as $containedObject) {
517
                if (!$propertyValue->contains($containedObject)) {
518
                    $removedObjects[] = $containedObject;
519
                }
520
            }
521
        }
522
        return $removedObjects;
523
    }
524
525
    /**
526
     * Updates the fields defining the relation between the object and the parent object.
527
     *
528
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
529
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
530
     * @param string $parentPropertyName
531
     * @param int $sortingPosition
532
     */
533
    protected function attachObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName, $sortingPosition = 0)
534
    {
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
            $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
539
        } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
540
            $this->insertRelationInRelationtable($object, $parentObject, $parentPropertyName, $sortingPosition);
541
        }
542
    }
543
544
    /**
545
     * Updates the fields defining the relation between the object and the parent object.
546
     *
547
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
548
     * @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject
549
     * @param string $parentPropertyName
550
     * @param int $sortingPosition
551
     */
552
    protected function updateRelationOfObjectToParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0)
553
    {
554
        $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
555
        $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
556
        if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
557
            $this->attachObjectToParentObjectRelationHasMany($object, $parentObject, $parentPropertyName, $sortingPosition);
558
        } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
559
            $this->updateRelationInRelationTable($object, $parentObject, $parentPropertyName, $sortingPosition);
560
        }
561
    }
562
563
    /**
564
     * Updates fields defining the relation between the object and the parent object in relation has-many.
565
     *
566
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
567
     * @param \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject
568
     * @param string $parentPropertyName
569
     * @param int $sortingPosition
570
     * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
571
     */
572
    protected function attachObjectToParentObjectRelationHasMany(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity $parentObject, $parentPropertyName, $sortingPosition = 0)
573
    {
574
        $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
575
        $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
576
        if ($parentColumnMap->getTypeOfRelation() !== \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
577
            throw new \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException(
578
                'Parent column relation type is ' . $parentColumnMap->getTypeOfRelation() .
579
                ' but should be ' . \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY,
580
                1345368105
581
            );
582
        }
583
        $row = [];
584
        $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
585
        if ($parentKeyFieldName !== null) {
586
            $row[$parentKeyFieldName] = $parentObject->getUid();
587
            $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
588
            if ($parentTableFieldName !== null) {
589
                $row[$parentTableFieldName] = $parentDataMap->getTableName();
590
            }
591
            $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
592
            if (is_array($relationTableMatchFields)) {
593
                $row = array_merge($relationTableMatchFields, $row);
594
            }
595
        }
596
        $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
597
        if (!empty($childSortByFieldName)) {
598
            $row[$childSortByFieldName] = $sortingPosition;
599
        }
600
        if (!empty($row)) {
601
            $this->updateObject($object, $row);
602
        }
603
    }
604
605
    /**
606
     * Updates the fields defining the relation between the object and the parent object.
607
     *
608
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
609
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject
610
     * @param string $parentPropertyName
611
     */
612
    protected function detachObjectFromParentObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
613
    {
614
        $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
615
        $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
616
        if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
617
            $row = [];
618
            $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
619
            if ($parentKeyFieldName !== null) {
620
                $row[$parentKeyFieldName] = 0;
621
                $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
622
                if ($parentTableFieldName !== null) {
623
                    $row[$parentTableFieldName] = '';
624
                }
625
                $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
626
                if (is_array($relationTableMatchFields) && !empty($relationTableMatchFields)) {
627
                    $row = array_merge(array_fill_keys(array_keys($relationTableMatchFields), ''), $row);
628
                }
629
            }
630
            $childSortByFieldName = $parentColumnMap->getChildSortByFieldName();
631
            if (!empty($childSortByFieldName)) {
632
                $row[$childSortByFieldName] = 0;
633
            }
634
            if (!empty($row)) {
635
                $this->updateObject($object, $row);
636
            }
637
        } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
638
            $this->deleteRelationFromRelationtable($object, $parentObject, $parentPropertyName);
639
        }
640
    }
641
642
    /**
643
     * Inserts an object in the storage backend
644
     *
645
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be insterted in the storage
646
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parentobject.
647
     * @param string $parentPropertyName
648
     */
649
    protected function insertObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject = null, $parentPropertyName = '')
650
    {
651
        if ($object instanceof \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject) {
652
            $result = $this->getUidOfAlreadyPersistedValueObject($object);
653
            if ($result !== false) {
654
                $object->_setProperty('uid', (int)$result);
655
                return;
656
            }
657
        }
658
        $dataMap = $this->dataMapper->getDataMap(get_class($object));
659
        $row = [];
660
        $properties = $object->_getProperties();
661
        foreach ($properties as $propertyName => $propertyValue) {
662
            if (!$dataMap->isPersistableProperty($propertyName) || $this->propertyValueIsLazyLoaded($propertyValue)) {
663
                continue;
664
            }
665
            $columnMap = $dataMap->getColumnMap($propertyName);
666
            if ($columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_ONE) {
667
                $row[$columnMap->getColumnName()] = 0;
668
            } elseif ($columnMap->getTypeOfRelation() !== ColumnMap::RELATION_NONE) {
669
                if ($columnMap->getParentKeyFieldName() === null) {
670
                    // CSV type relation
671
                    $row[$columnMap->getColumnName()] = '';
672
                } else {
673
                    // MM type relation
674
                    $row[$columnMap->getColumnName()] = 0;
675
                }
676
            } elseif ($propertyValue !== null) {
677
                $row[$columnMap->getColumnName()] = $this->getPlainValue($propertyValue, $columnMap);
678
            }
679
        }
680
        $this->addCommonFieldsToRow($object, $row);
681
        if ($dataMap->getLanguageIdColumnName() !== null && $object->_getProperty('_languageUid') === null) {
682
            $row[$dataMap->getLanguageIdColumnName()] = 0;
683
            $object->_setProperty('_languageUid', 0);
684
        }
685
        if ($dataMap->getTranslationOriginColumnName() !== null) {
686
            $row[$dataMap->getTranslationOriginColumnName()] = 0;
687
        }
688
        if ($dataMap->getTranslationOriginDiffSourceName() !== null) {
689
            $row[$dataMap->getTranslationOriginDiffSourceName()] = '';
690
        }
691
        if ($parentObject !== null && $parentPropertyName) {
692
            $parentColumnDataMap = $this->dataMapper->getDataMap(get_class($parentObject))->getColumnMap($parentPropertyName);
693
            $relationTableMatchFields = $parentColumnDataMap->getRelationTableMatchFields();
694
            if (is_array($relationTableMatchFields)) {
695
                $row = array_merge($relationTableMatchFields, $row);
696
            }
697
            if ($parentColumnDataMap->getParentKeyFieldName() !== null) {
698
                $row[$parentColumnDataMap->getParentKeyFieldName()] = (int)$parentObject->getUid();
699
            }
700
        }
701
        $uid = $this->storageBackend->addRow($dataMap->getTableName(), $row);
702
        $object->_setProperty('uid', (int)$uid);
703
        $object->setPid((int)$row['pid']);
704
        if ((int)$uid >= 1) {
705
            $this->emitAfterInsertObjectSignal($object);
706
        }
707
        $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
708
        if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
709
            $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $uid);
710
        }
711
        $this->session->registerObject($object, $uid);
712
        if ((int)$uid >= 1) {
713
            $this->emitEndInsertObjectSignal($object);
714
        }
715
    }
716
717
    /**
718
     * Emits a signal after an object was added to the storage
719
     *
720
     * @param DomainObjectInterface $object
721
     */
722
    protected function emitAfterInsertObjectSignal(DomainObjectInterface $object)
723
    {
724
        $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterInsertObject', [$object]);
725
    }
726
727
    /**
728
     * Emits a signal after an object was registered in persistence session
729
     * This signal replaces the afterInsertObject signal which is now deprecated
730
     *
731
     * @param DomainObjectInterface $object
732
     */
733
    protected function emitEndInsertObjectSignal(DomainObjectInterface $object)
734
    {
735
        $this->signalSlotDispatcher->dispatch(__CLASS__, 'endInsertObject', [$object]);
736
    }
737
738
    /**
739
     * Tests, if the given Value Object already exists in the storage backend and if so, it returns the uid.
740
     *
741
     * @param \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object The object to be tested
742
     * @return mixed The matching uid if an object was found, else FALSE
743
     */
744
    protected function getUidOfAlreadyPersistedValueObject(\TYPO3\CMS\Extbase\DomainObject\AbstractValueObject $object)
745
    {
746
        return $this->storageBackend->getUidOfAlreadyPersistedValueObject($object);
747
    }
748
749
    /**
750
     * Inserts mm-relation into a relation table
751
     *
752
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
753
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
754
     * @param string $propertyName The name of the parent object's property where the related objects are stored in
755
     * @param int $sortingPosition Defaults to NULL
756
     * @return int The uid of the inserted row
757
     */
758
    protected function insertRelationInRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, $sortingPosition = null)
759
    {
760
        $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
761
        $columnMap = $dataMap->getColumnMap($propertyName);
762
        $parentUid = $parentObject->getUid();
763
        if ($parentObject->_getProperty('_localizedUid') !== null) {
764
            $parentUid = $parentObject->_getProperty('_localizedUid');
765
        }
766
        $row = [
767
            $columnMap->getParentKeyFieldName() => (int)$parentUid,
768
            $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
769
            $columnMap->getChildSortByFieldName() => !is_null($sortingPosition) ? (int)$sortingPosition : 0
770
        ];
771
        $relationTableName = $columnMap->getRelationTableName();
772
        if ($columnMap->getRelationTablePageIdColumnName() !== null) {
773
            $row[$columnMap->getRelationTablePageIdColumnName()] = $this->determineStoragePageIdForNewRecord();
774
        }
775
        $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
776
        if (is_array($relationTableMatchFields)) {
777
            $row = array_merge($relationTableMatchFields, $row);
778
        }
779
        $relationTableInsertFields = $columnMap->getRelationTableInsertFields();
780
        if (is_array($relationTableInsertFields)) {
781
            $row = array_merge($relationTableInsertFields, $row);
782
        }
783
        $res = $this->storageBackend->addRow($relationTableName, $row, true);
784
        return $res;
785
    }
786
787
    /**
788
     * Updates mm-relation in a relation table
789
     *
790
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The related object
791
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
792
     * @param string $propertyName The name of the parent object's property where the related objects are stored in
793
     * @param int $sortingPosition Defaults to NULL
794
     * @return bool TRUE if update was successfully
795
     */
796
    protected function updateRelationInRelationTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $propertyName, $sortingPosition = 0)
797
    {
798
        $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
799
        $columnMap = $dataMap->getColumnMap($propertyName);
800
        $row = [
801
            $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
802
            $columnMap->getChildKeyFieldName() => (int)$object->getUid(),
803
            $columnMap->getChildSortByFieldName() => (int)$sortingPosition
804
        ];
805
        $relationTableName = $columnMap->getRelationTableName();
806
        $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
807
        if (is_array($relationTableMatchFields)) {
808
            $row = array_merge($relationTableMatchFields, $row);
809
        }
810
        $res = $this->storageBackend->updateRelationTableRow(
811
            $relationTableName,
812
            $row
813
        );
814
        return $res;
815
    }
816
817
    /**
818
     * Delete all mm-relations of a parent from a relation table
819
     *
820
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
821
     * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
822
     * @return bool TRUE if delete was successfully
823
     */
824
    protected function deleteAllRelationsFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
825
    {
826
        $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
827
        $columnMap = $dataMap->getColumnMap($parentPropertyName);
828
        $relationTableName = $columnMap->getRelationTableName();
829
        $relationMatchFields = [
830
            $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
831
        ];
832
        $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
833
        if (is_array($relationTableMatchFields)) {
834
            $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields);
835
        }
836
        $res = $this->storageBackend->removeRow($relationTableName, $relationMatchFields, false);
837
        return $res;
838
    }
839
840
    /**
841
     * Delete an mm-relation from a relation table
842
     *
843
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $relatedObject The related object
844
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
845
     * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
846
     * @return bool
847
     */
848
    protected function deleteRelationFromRelationtable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $relatedObject, \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
849
    {
850
        $dataMap = $this->dataMapper->getDataMap(get_class($parentObject));
851
        $columnMap = $dataMap->getColumnMap($parentPropertyName);
852
        $relationTableName = $columnMap->getRelationTableName();
853
        $relationMatchFields = [
854
            $columnMap->getParentKeyFieldName() => (int)$parentObject->getUid(),
855
            $columnMap->getChildKeyFieldName() => (int)$relatedObject->getUid()
856
        ];
857
        $relationTableMatchFields = $columnMap->getRelationTableMatchFields();
858
        if (is_array($relationTableMatchFields)) {
859
            $relationMatchFields = array_merge($relationTableMatchFields, $relationMatchFields);
860
        }
861
        $res = $this->storageBackend->removeRow($relationTableName, $relationMatchFields, false);
862
        return $res;
863
    }
864
865
    /**
866
     * Fetches maximal value currently used for sorting field in parent table
867
     *
868
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject The parent object
869
     * @param string $parentPropertyName The name of the parent object's property where the related objects are stored in
870
     * @throws \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException
871
     * @return mixed the max value
872
     */
873
    protected function fetchMaxSortingFromParentTable(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $parentObject, $parentPropertyName)
874
    {
875
        $parentDataMap = $this->dataMapper->getDataMap(get_class($parentObject));
876
        $parentColumnMap = $parentDataMap->getColumnMap($parentPropertyName);
877
        if ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
878
            $tableName = $parentColumnMap->getChildTableName();
879
            $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
880
881
            if (empty($sortByFieldName)) {
882
                return false;
883
            }
884
            $matchFields = [];
885
            $parentKeyFieldName = $parentColumnMap->getParentKeyFieldName();
886
            if ($parentKeyFieldName !== null) {
887
                $matchFields[$parentKeyFieldName] = $parentObject->getUid();
888
                $parentTableFieldName = $parentColumnMap->getParentTableFieldName();
889
                if ($parentTableFieldName !== null) {
890
                    $matchFields[$parentTableFieldName] = $parentDataMap->getTableName();
891
                }
892
            }
893
894
            if (empty($matchFields)) {
895
                return false;
896
            }
897
        } elseif ($parentColumnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
898
            $tableName = $parentColumnMap->getRelationTableName();
899
            $sortByFieldName = $parentColumnMap->getChildSortByFieldName();
900
901
            $matchFields = [
902
                $parentColumnMap->getParentKeyFieldName() => (int)$parentObject->getUid()
903
            ];
904
905
            $relationTableMatchFields = $parentColumnMap->getRelationTableMatchFields();
906
            if (is_array($relationTableMatchFields)) {
907
                $matchFields = array_merge($relationTableMatchFields, $matchFields);
908
            }
909
        } else {
910
            throw new \TYPO3\CMS\Extbase\Persistence\Exception\IllegalRelationTypeException('Unexpected parent column relation type: ' . $parentColumnMap->getTypeOfRelation(), 1345368106);
911
        }
912
913
        $result = $this->storageBackend->getMaxValueFromTable(
914
            $tableName,
915
            $matchFields,
916
            $sortByFieldName
917
        );
918
        return $result;
919
    }
920
921
    /**
922
     * Updates a given object in the storage
923
     *
924
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be updated
925
     * @param array $row Row to be stored
926
     * @return bool
927
     */
928
    protected function updateObject(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array $row)
929
    {
930
        $dataMap = $this->dataMapper->getDataMap(get_class($object));
931
        $this->addCommonFieldsToRow($object, $row);
932
        $row['uid'] = $object->getUid();
933
        if ($dataMap->getLanguageIdColumnName() !== null) {
934
            $row[$dataMap->getLanguageIdColumnName()] = (int)$object->_getProperty('_languageUid');
935
            if ($object->_getProperty('_localizedUid') !== null) {
936
                $row['uid'] = $object->_getProperty('_localizedUid');
937
            }
938
        }
939
        $res = $this->storageBackend->updateRow($dataMap->getTableName(), $row);
940
        if ($res === true) {
941
            $this->emitAfterUpdateObjectSignal($object);
942
        }
943
        $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
944
        if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
945
            $this->referenceIndex->updateRefIndexTable($dataMap->getTableName(), $row['uid']);
946
        }
947
        return $res;
948
    }
949
950
    /**
951
     * Emits a signal after an object was updated in storage
952
     *
953
     * @param DomainObjectInterface $object
954
     */
955
    protected function emitAfterUpdateObjectSignal(DomainObjectInterface $object)
956
    {
957
        $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterUpdateObject', [$object]);
958
    }
959
960
    /**
961
     * Emits a signal after an object was persisted
962
     *
963
     * @param DomainObjectInterface $object
964
     */
965
    protected function emitAfterPersistObjectSignal(DomainObjectInterface $object)
966
    {
967
        $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterPersistObject', [$object]);
968
    }
969
970
    /**
971
     * Adds common databse fields to a row
972
     *
973
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
974
     * @param array &$row
975
     */
976
    protected function addCommonFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array &$row)
977
    {
978
        $dataMap = $this->dataMapper->getDataMap(get_class($object));
979
        $this->addCommonDateFieldsToRow($object, $row);
980
        if ($dataMap->getRecordTypeColumnName() !== null && $dataMap->getRecordType() !== null) {
981
            $row[$dataMap->getRecordTypeColumnName()] = $dataMap->getRecordType();
982
        }
983
        if ($object->_isNew() && !isset($row['pid'])) {
984
            $row['pid'] = $this->determineStoragePageIdForNewRecord($object);
985
        }
986
    }
987
988
    /**
989
     * Adjustes the common date fields of the given row to the current time
990
     *
991
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
992
     * @param array &$row The row to be updated
993
     */
994
    protected function addCommonDateFieldsToRow(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, array &$row)
995
    {
996
        $dataMap = $this->dataMapper->getDataMap(get_class($object));
997
        if ($object->_isNew() && $dataMap->getCreationDateColumnName() !== null) {
998
            $row[$dataMap->getCreationDateColumnName()] = $GLOBALS['EXEC_TIME'];
999
        }
1000
        if ($dataMap->getModificationDateColumnName() !== null) {
1001
            $row[$dataMap->getModificationDateColumnName()] = $GLOBALS['EXEC_TIME'];
1002
        }
1003
    }
1004
1005
    /**
1006
     * Iterate over deleted aggregate root objects and process them
1007
     */
1008
    protected function processDeletedObjects()
1009
    {
1010
        foreach ($this->deletedEntities as $entity) {
1011
            if ($this->session->hasObject($entity)) {
1012
                $this->removeEntity($entity);
1013
                $this->session->unregisterReconstitutedEntity($entity);
1014
                $this->session->unregisterObject($entity);
1015
            }
1016
        }
1017
        $this->deletedEntities = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage();
1018
    }
1019
1020
    /**
1021
     * Deletes an object
1022
     *
1023
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to be removed from the storage
1024
     * @param bool $markAsDeleted Whether to just flag the row deleted (default) or really delete it
1025
     */
1026
    protected function removeEntity(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object, $markAsDeleted = true)
1027
    {
1028
        $dataMap = $this->dataMapper->getDataMap(get_class($object));
1029
        $tableName = $dataMap->getTableName();
1030
        if ($markAsDeleted === true && $dataMap->getDeletedFlagColumnName() !== null) {
1031
            $deletedColumnName = $dataMap->getDeletedFlagColumnName();
1032
            $row = [
1033
                'uid' => $object->getUid(),
1034
                $deletedColumnName => 1
1035
            ];
1036
            $this->addCommonDateFieldsToRow($object, $row);
1037
            $res = $this->storageBackend->updateRow($tableName, $row);
1038
        } else {
1039
            $res = $this->storageBackend->removeRow($tableName, ['uid' => $object->getUid()]);
1040
        }
1041
        if ($res === true) {
1042
            $this->emitAfterRemoveObjectSignal($object);
1043
        }
1044
        $this->removeRelatedObjects($object);
1045
        $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
1046
        if ($frameworkConfiguration['persistence']['updateReferenceIndex'] === '1') {
1047
            $this->referenceIndex->updateRefIndexTable($tableName, $object->getUid());
1048
        }
1049
    }
1050
1051
    /**
1052
     * Emits a signal after an object was removed from storage
1053
     *
1054
     * @param DomainObjectInterface $object
1055
     */
1056
    protected function emitAfterRemoveObjectSignal(DomainObjectInterface $object)
1057
    {
1058
        $this->signalSlotDispatcher->dispatch(__CLASS__, 'afterRemoveObject', [$object]);
1059
    }
1060
1061
    /**
1062
     * Remove related objects
1063
     *
1064
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object The object to scanned for related objects
1065
     */
1066
    protected function removeRelatedObjects(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object)
1067
    {
1068
        $className = get_class($object);
1069
        $dataMap = $this->dataMapper->getDataMap($className);
1070
        $classSchema = $this->reflectionService->getClassSchema($className);
1071
        $properties = $object->_getProperties();
1072
        foreach ($properties as $propertyName => $propertyValue) {
1073
            $columnMap = $dataMap->getColumnMap($propertyName);
1074
            if ($columnMap === null) {
1075
                continue;
1076
            }
1077
            $propertyMetaData = $classSchema->getProperty($propertyName);
1078
            if ($propertyMetaData['annotations']['cascade'] === 'remove') {
1079
                if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
1080
                    foreach ($propertyValue as $containedObject) {
1081
                        $this->removeEntity($containedObject);
1082
                    }
1083
                } elseif ($propertyValue instanceof \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface) {
1084
                    $this->removeEntity($propertyValue);
1085
                }
1086
            } elseif ($dataMap->getDeletedFlagColumnName() === null
1087
                && $columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY
1088
            ) {
1089
                $this->deleteAllRelationsFromRelationtable($object, $propertyName);
1090
            }
1091
        }
1092
    }
1093
1094
    /**
1095
     * Determine the storage page ID for a given NEW record
1096
     *
1097
     * This does the following:
1098
     * - If the domain object has an accessible property 'pid' (i.e. through a getPid() method), that is used to store the record.
1099
     * - If there is a TypoScript configuration "classes.CLASSNAME.newRecordStoragePid", that is used to store new records.
1100
     * - If there is no such TypoScript configuration, it uses the first value of The "storagePid" taken for reading records.
1101
     *
1102
     * @param \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object
1103
     * @return int the storage Page ID where the object should be stored
1104
     */
1105
    protected function determineStoragePageIdForNewRecord(\TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object = null)
1106
    {
1107
        $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
1108
        if ($object !== null) {
1109
            if (\TYPO3\CMS\Extbase\Reflection\ObjectAccess::isPropertyGettable($object, 'pid')) {
1110
                $pid = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getProperty($object, 'pid');
1111
                if (isset($pid)) {
1112
                    return (int)$pid;
1113
                }
1114
            }
1115
            $className = get_class($object);
1116
            if (isset($frameworkConfiguration['persistence']['classes'][$className]) && !empty($frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'])) {
1117
                return (int)$frameworkConfiguration['persistence']['classes'][$className]['newRecordStoragePid'];
1118
            }
1119
        }
1120
        $storagePidList = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $frameworkConfiguration['persistence']['storagePid']);
1121
        return (int)$storagePidList[0];
1122
    }
1123
1124
    /**
1125
     * Returns a plain value
1126
     *
1127
     * i.e. objects are flattened out if possible.
1128
     * Checks explicitly for null values as DataMapper's getPlainValue would convert this to 'NULL'
1129
     *
1130
     * @param mixed $input The value that will be converted
1131
     * @param ColumnMap $columnMap Optional column map for retrieving the date storage format
1132
     * @return int|string|null
1133
     */
1134
    protected function getPlainValue($input, ColumnMap $columnMap = null)
1135
    {
1136
        return $input !== null
1137
            ? $this->dataMapper->getPlainValue($input, $columnMap)
1138
            : null;
1139
    }
1140
}
1141