• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 8156443696

05 Mar 2024 12:43PM UTC coverage: 57.081% (+0.2%) from 56.922%
8156443696

push

github

web-flow
Merge pull request #6201 from soyuka/merge-main

Merge 3.2

33 of 45 new or added lines in 9 files covered. (73.33%)

109 existing lines in 5 files now uncovered.

9553 of 16736 relevant lines covered (57.08%)

41.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.43
/src/Doctrine/EventListener/PurgeHttpCacheListener.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
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 ApiPlatform\Doctrine\EventListener;
15

16
use ApiPlatform\Api\IriConverterInterface as LegacyIriConverterInterface;
17
use ApiPlatform\Api\ResourceClassResolverInterface as LegacyResourceClassResolverInterface;
18
use ApiPlatform\Exception\InvalidArgumentException;
19
use ApiPlatform\Exception\OperationNotFoundException;
20
use ApiPlatform\Exception\RuntimeException;
21
use ApiPlatform\HttpCache\PurgerInterface;
22
use ApiPlatform\Metadata\GetCollection;
23
use ApiPlatform\Metadata\IriConverterInterface;
24
use ApiPlatform\Metadata\ResourceClassResolverInterface;
25
use ApiPlatform\Metadata\UrlGeneratorInterface;
26
use ApiPlatform\Metadata\Util\ClassInfoTrait;
27
use Doctrine\Common\Util\ClassUtils;
28
use Doctrine\ORM\EntityManagerInterface;
29
use Doctrine\ORM\Event\OnFlushEventArgs;
30
use Doctrine\ORM\Event\PreUpdateEventArgs;
31
use Doctrine\ORM\Mapping\AssociationMapping;
32
use Doctrine\ORM\PersistentCollection;
33
use Symfony\Component\PropertyAccess\PropertyAccess;
34
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
35

36
/**
37
 * Purges responses containing modified entities from the proxy cache.
38
 *
39
 * @author Kévin Dunglas <dunglas@gmail.com>
40
 */
41
final class PurgeHttpCacheListener
42
{
43
    use ClassInfoTrait;
44
    private readonly PropertyAccessorInterface $propertyAccessor;
45
    private array $tags = [];
46

47
    public function __construct(private readonly PurgerInterface $purger, private readonly IriConverterInterface|LegacyIriConverterInterface $iriConverter, private readonly ResourceClassResolverInterface|LegacyResourceClassResolverInterface $resourceClassResolver, ?PropertyAccessorInterface $propertyAccessor = null)
48
    {
49
        $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
48✔
50
    }
51

52
    /**
53
     * Collects tags from the previous and the current version of the updated entities to purge related documents.
54
     */
55
    public function preUpdate(PreUpdateEventArgs $eventArgs): void
56
    {
57
        $object = $eventArgs->getObject();
8✔
58
        $this->gatherResourceAndItemTags($object, true);
8✔
59

60
        $changeSet = $eventArgs->getEntityChangeSet();
8✔
61
        // @phpstan-ignore-next-line
62
        $objectManager = method_exists($eventArgs, 'getObjectManager') ? $eventArgs->getObjectManager() : $eventArgs->getEntityManager();
8✔
63
        $associationMappings = $objectManager->getClassMetadata(ClassUtils::getClass($eventArgs->getObject()))->getAssociationMappings();
8✔
64

65
        foreach ($changeSet as $key => $value) {
8✔
66
            if (!isset($associationMappings[$key])) {
8✔
67
                continue;
4✔
68
            }
69

70
            $this->addTagsFor($value[0]);
4✔
71
            $this->addTagsFor($value[1]);
4✔
72
        }
73
    }
74

75
    /**
76
     * Collects tags from inserted and deleted entities, including relations.
77
     */
78
    public function onFlush(OnFlushEventArgs $eventArgs): void
79
    {
80
        // @phpstan-ignore-next-line
81
        $em = method_exists($eventArgs, 'getObjectManager') ? $eventArgs->getObjectManager() : $eventArgs->getEntityManager();
40✔
82
        $uow = $em->getUnitOfWork();
40✔
83

84
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
40✔
85
            $this->gatherResourceAndItemTags($entity, false);
40✔
86
            $this->gatherRelationTags($em, $entity);
40✔
87
        }
88

89
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
40✔
90
            $this->gatherResourceAndItemTags($entity, true);
4✔
91
            $this->gatherRelationTags($em, $entity);
4✔
92
        }
93

94
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
40✔
95
            $this->gatherResourceAndItemTags($entity, true);
4✔
96
            $this->gatherRelationTags($em, $entity);
4✔
97
        }
98
    }
99

100
    /**
101
     * Purges tags collected during this request, and clears the tag list.
102
     */
103
    public function postFlush(): void
104
    {
105
        if (empty($this->tags)) {
44✔
106
            return;
4✔
107
        }
108

109
        $this->purger->purge(array_values($this->tags));
40✔
110

111
        $this->tags = [];
40✔
112
    }
113

114
    private function gatherResourceAndItemTags(object $entity, bool $purgeItem): void
115
    {
116
        try {
117
            $resourceClass = $this->resourceClassResolver->getResourceClass($entity);
48✔
118
            $iri = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, new GetCollection());
48✔
119
            $this->tags[$iri] = $iri;
44✔
120

121
            if ($purgeItem) {
44✔
122
                $this->addTagForItem($entity);
44✔
123
            }
124
        } catch (OperationNotFoundException|InvalidArgumentException) {
8✔
125
        }
126
    }
127

128
    private function gatherRelationTags(EntityManagerInterface $em, object $entity): void
129
    {
130
        $associationMappings = $em->getClassMetadata(ClassUtils::getClass($entity))->getAssociationMappings();
40✔
131
        /** @var array|AssociationMapping $associationMapping according to the version of doctrine orm */
132
        foreach ($associationMappings as $property => $associationMapping) {
40✔
133
            if ($associationMapping instanceof AssociationMapping && ($associationMapping->targetEntity ?? null) && !$this->resourceClassResolver->isResourceClass($associationMapping->targetEntity)) {
28✔
NEW
134
                return;
×
135
            }
136

137
            if (
138
                \is_array($associationMapping)
28✔
139
                && \array_key_exists('targetEntity', $associationMapping)
28✔
140
                && !$this->resourceClassResolver->isResourceClass($associationMapping['targetEntity'])) {
28✔
141
                return;
4✔
142
            }
143

144
            if ($this->propertyAccessor->isReadable($entity, $property)) {
28✔
145
                $this->addTagsFor($this->propertyAccessor->getValue($entity, $property));
28✔
146
            }
147
        }
148
    }
149

150
    private function addTagsFor(mixed $value): void
151
    {
152
        if (!$value || \is_scalar($value)) {
32✔
153
            return;
16✔
154
        }
155

156
        if (!is_iterable($value)) {
28✔
157
            $this->addTagForItem($value);
12✔
158

159
            return;
12✔
160
        }
161

162
        if ($value instanceof PersistentCollection) {
16✔
163
            $value = clone $value;
16✔
164
        }
165

166
        foreach ($value as $v) {
16✔
167
            $this->addTagForItem($v);
×
168
        }
169
    }
170

171
    private function addTagForItem(mixed $value): void
172
    {
173
        if (!$this->resourceClassResolver->isResourceClass($this->getObjectClass($value))) {
16✔
174
            return;
4✔
175
        }
176

177
        try {
178
            $iri = $this->iriConverter->getIriFromResource($value);
12✔
179
            $this->tags[$iri] = $iri;
8✔
180
        } catch (RuntimeException|InvalidArgumentException) {
4✔
181
        }
182
    }
183
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc