Passed
Push — main ( c28b1c...6868cf )
by Daniel
04:09
created

gatherRelationResourceClasses()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 9
nc 4
nop 2
dl 0
loc 12
ccs 0
cts 9
cp 0
crap 30
rs 9.6111
c 1
b 0
f 0
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\EntityManagerInterface;
23
use Doctrine\ORM\EntityRepository;
24
use Doctrine\ORM\Event\OnFlushEventArgs;
25
use Doctrine\ORM\Event\PreUpdateEventArgs;
26
use Doctrine\ORM\PersistentCollection;
27
use Doctrine\Persistence\ManagerRegistry;
28
use Doctrine\Persistence\ObjectRepository;
29
use Silverback\ApiComponentsBundle\Entity\Component\Collection;
30
use Silverback\ApiComponentsBundle\Entity\Core\ComponentPosition;
31
use Silverback\ApiComponentsBundle\Entity\Core\PageDataInterface;
32
use Silverback\ApiComponentsBundle\HttpCache\ResourceChangedPropagatorInterface;
33
use Symfony\Component\PropertyAccess\PropertyAccess;
34
use Symfony\Component\PropertyAccess\PropertyAccessor;
35
36
trait DoctrineResourceFlushTrait
37
{
38
    private array $pageDataPropertiesChanged = [];
39
    private PropertyAccessor $propertyAccessor;
40
    private ObjectRepository|EntityRepository $collectionRepository;
41
    private ObjectRepository|EntityRepository $positionRepository;
42
    private array $resourceIris = [];
43
44
    public function __construct(
45
        private readonly IriConverterInterface $iriConverter,
46
        ManagerRegistry $entityManager,
47
        private readonly ResourceChangedPropagatorInterface $resourceChangedPropagator,
48
        private readonly ResourceClassResolverInterface $resourceClassResolver
49
    ) {
50
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
51
        $this->collectionRepository = $entityManager->getRepository(Collection::class);
52
        $this->positionRepository = $entityManager->getRepository(ComponentPosition::class);
53
    }
54
55
    public function preUpdate(PreUpdateEventArgs $eventArgs): void
56
    {
57
        $object = $eventArgs->getObject();
58
        $this->collectUpdatedResource($object, 'updated');
59
60
        $changeSet = $eventArgs->getEntityChangeSet();
61
        $associationMappings = $this->getAssociationMappings($eventArgs->getEntityManager(), $eventArgs->getObject());
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\Event\Lifec...rgs::getEntityManager() has been deprecated: 2.13. Use {@see getObjectManager} instead. ( Ignorable by Annotation )

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

61
        $associationMappings = $this->getAssociationMappings(/** @scrutinizer ignore-deprecated */ $eventArgs->getEntityManager(), $eventArgs->getObject());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
62
63
        if ($object instanceof PageDataInterface) {
64
            $this->pageDataPropertiesChanged = array_keys($changeSet);
65
        }
66
67
        foreach ($changeSet as $field => $value) {
68
            if (!isset($associationMappings[$field])) {
69
                continue;
70
            }
71
72
            $this->collectUpdatedResource($value[0], 'updated');
73
            $this->collectUpdatedResource($value[1], 'updated');
74
        }
75
    }
76
77
    public function onFlush(OnFlushEventArgs $eventArgs): void
78
    {
79
        $em = $eventArgs->getObjectManager();
80
        $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

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