Passed
Push — main ( 975c76...182af0 )
by Daniel
04:23 queued 27s
created

collectUpdatedResource()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 7
ccs 0
cts 5
cp 0
crap 6
rs 10
1
<?php
2
3
/*
4
 * This file is part of the Silverback API Components Bundle Project
5
 *
6
 * (c) Daniel West <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Silverback\ApiComponentsBundle\EventListener\Doctrine;
15
16
use ApiPlatform\Api\IriConverterInterface;
17
use ApiPlatform\Api\ResourceClassResolverInterface;
18
use ApiPlatform\Api\UrlGeneratorInterface;
19
use ApiPlatform\Exception\InvalidArgumentException;
20
use ApiPlatform\Metadata\GetCollection;
21
use Doctrine\Common\Util\ClassUtils;
22
use Doctrine\ORM\EntityRepository;
23
use Doctrine\ORM\Event\OnFlushEventArgs;
24
use Doctrine\ORM\Event\PostFlushEventArgs;
25
use Doctrine\ORM\Event\PreUpdateEventArgs;
26
use Doctrine\ORM\PersistentCollection;
27
use Doctrine\Persistence\ManagerRegistry;
28
use Doctrine\Persistence\ObjectManager;
29
use Doctrine\Persistence\ObjectRepository;
30
use Silverback\ApiComponentsBundle\Entity\Component\Collection;
31
use Silverback\ApiComponentsBundle\Entity\Core\ComponentPosition;
32
use Silverback\ApiComponentsBundle\Entity\Core\PageDataInterface;
33
use Silverback\ApiComponentsBundle\HttpCache\ResourceChangedPropagatorInterface;
34
use Symfony\Component\PropertyAccess\PropertyAccess;
35
use Symfony\Component\PropertyAccess\PropertyAccessor;
36
37
trait DoctrineResourceFlushTrait
38
{
39
    private array $pageDataPropertiesChanged = [];
40
    private PropertyAccessor $propertyAccessor;
41
    private ObjectRepository|EntityRepository $collectionRepository;
42
    private ObjectRepository|EntityRepository $positionRepository;
43
    private array $resourceIris = [];
44
    private array $gatherRelatedForEntities = [];
45
46
    public function __construct(
47
        private readonly IriConverterInterface $iriConverter,
48
        ManagerRegistry $entityManager,
49
        private readonly ResourceChangedPropagatorInterface $resourceChangedPropagator,
50
        private readonly ResourceClassResolverInterface $resourceClassResolver
51
    ) {
52
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
53
        $this->collectionRepository = $entityManager->getRepository(Collection::class);
54
        $this->positionRepository = $entityManager->getRepository(ComponentPosition::class);
55
    }
56
57
    public function preUpdate(PreUpdateEventArgs $eventArgs): void
58
    {
59
        $object = $eventArgs->getObject();
60
        $this->collectUpdatedResource($object, 'updated');
61
62
        $changeSet = $eventArgs->getEntityChangeSet();
63
        $associationMappings = $this->getAssociationMappings($eventArgs->getObjectManager(), $eventArgs->getObject());
64
65
        if ($object instanceof PageDataInterface) {
66
            $this->pageDataPropertiesChanged = array_keys($changeSet);
67
        }
68
69
        foreach ($changeSet as $field => $value) {
70
            if (!isset($associationMappings[$field])) {
71
                continue;
72
            }
73
74
            $this->collectUpdatedResource($value[0], 'updated');
75
            $this->collectUpdatedResource($value[1], 'updated');
76
        }
77
    }
78
79
    public function onFlush(OnFlushEventArgs $eventArgs): void
80
    {
81
        $em = $eventArgs->getObjectManager();
82
        $uow = $em->getUnitOfWork();
0 ignored issues
show
Bug introduced by
The method getUnitOfWork() does not exist on Doctrine\Persistence\ObjectManager. It seems like you code against a sub-type of said class. However, the method does not exist in Doctrine\Persistence\ObjectManagerDecorator. Are you sure you never get one of those? ( Ignorable by Annotation )

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

82
        /** @scrutinizer ignore-call */ 
83
        $uow = $em->getUnitOfWork();
Loading history...
83
        $this->gatherRelatedForEntities = [];
84
85
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
86
            $this->collectUpdatedResource($entity, 'created');
87
            $this->gatherRelatedForEntities[] = $entity;
88
        }
89
90
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
91
            $this->collectUpdatedResource($entity, 'updated');
92
            $this->gatherRelatedForEntities[] = $entity;
93
        }
94
95
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
96
            $this->collectUpdatedResource($entity, 'deleted');
97
            $this->gatherRelatedForEntities[] = $entity;
98
        }
99
    }
100
101
    public function postFlush(PostFlushEventArgs $eventArgs): void
102
    {
103
        foreach ($this->gatherRelatedForEntities as $object) {
104
            $this->gatherRelationResourceClasses($eventArgs->getObjectManager(), $object);
105
        }
106
        $this->addResourcesToPurge($this->gatherResourcesForPositionsWithPageDataProperties(), 'updated');
107
        $this->addResourcesToPurge($this->gatherIrisForCollectionResources(), 'updated');
108
        $this->purgeResources();
109
    }
110
111
    private function gatherRelationResourceClasses(ObjectManager $em, $entity): void
112
    {
113
        $associationMappings = $this->getAssociationMappings($em, $entity);
114
        foreach (array_keys($associationMappings) as $property) {
115
            if ($this->propertyAccessor->isReadable($entity, $property)) {
116
                $value = $this->propertyAccessor->getValue($entity, $property);
117
                if (!$value) {
118
                    return;
119
                }
120
                if ($value instanceof PersistentCollection) {
121
                    foreach ($value as $item) {
122
                        if (!$item) {
123
                            continue;
124
                        }
125
                        $em->refresh($item);
126
                        $this->collectUpdatedResource($item, 'updated');
127
                    }
128
                } else {
129
                    $em->refresh($value);
130
                    $this->collectUpdatedResource($value, 'updated');
131
                }
132
            }
133
        }
134
    }
135
136
    private function gatherResourcesForPositionsWithPageDataProperties(): array
137
    {
138
        $positionResources = [];
139
        foreach ($this->pageDataPropertiesChanged as $pageDataProperty) {
140
            $positions = $this->positionRepository->findBy([
141
                'pageDataProperty' => $pageDataProperty,
142
            ]);
143
            foreach ($positions as $position) {
144
                $positionResources[] = $position;
145
            }
146
        }
147
148
        return $positionResources;
149
    }
150
151
    private function gatherIrisForCollectionResources(): array
152
    {
153
        if (empty($this->resourceIris)) {
154
            return [];
155
        }
156
157
        $collectionResources = [];
158
        foreach ($this->resourceIris as $resourceIri) {
159
            $collections = $this->collectionRepository->findBy([
160
                'resourceIri' => $resourceIri,
161
            ]);
162
            foreach ($collections as $collection) {
163
                $collectionResources[] = $collection;
164
            }
165
        }
166
167
        $this->resourceIris = [];
168
        if (empty($collectionResources)) {
169
            return [];
170
        }
171
172
        return $collectionResources;
173
    }
174
175
    private function getAssociationMappings(ObjectManager $em, $entity): array
176
    {
177
        return $em->getClassMetadata(ClassUtils::getClass($entity))->getAssociationMappings();
178
    }
179
180
    private function collectUpdatedResource($resource, string $type): void
181
    {
182
        if (!$resource) {
183
            return;
184
        }
185
        $this->addResourceIris([$resource], $type);
186
        $this->resourceChangedPropagator->collectItems([$resource], $type);
187
    }
188
189
    private function addResourcesToPurge(array $resources, string $type): void
190
    {
191
        $this->addResourceIris($resources, $type);
192
        $this->resourceChangedPropagator->collectItems($resources, $type);
193
    }
194
195
    private function addResourceIris(array $resources, string $type): void
196
    {
197
        foreach ($resources as $resource) {
198
            try {
199
                $this->resourceChangedPropagator->collectResource($resource, $type);
200
201
                $resourceClass = $this->resourceClassResolver->getResourceClass($resource);
202
                $resourceIri = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, (new GetCollection())->withClass($resourceClass));
203
204
                if (!\in_array($resourceIri, $this->resourceIris, true)) {
205
                    $this->resourceIris[] = $resourceIri;
206
                }
207
            } catch (InvalidArgumentException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
208
            }
209
        }
210
    }
211
212
    private function purgeResources(): void
213
    {
214
        $this->resourceChangedPropagator->propagate();
215
        $this->resourceIris = [];
216
    }
217
}
218