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

api-platform / core / 3712739783

pending completion
3712739783

Pull #5254

github

GitHub
Merge 9dfa88fa6 into ac711530f
Pull Request #5254: [OpenApi] Add ApiResource::openapi and deprecate openapiContext

199 of 199 new or added lines in 6 files covered. (100.0%)

7494 of 12363 relevant lines covered (60.62%)

67.55 hits per line

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

0.0
/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;
17
use ApiPlatform\Api\ResourceClassResolverInterface;
18
use ApiPlatform\Api\UrlGeneratorInterface;
19
use ApiPlatform\Exception\InvalidArgumentException;
20
use ApiPlatform\Exception\OperationNotFoundException;
21
use ApiPlatform\Exception\RuntimeException;
22
use ApiPlatform\HttpCache\PurgerInterface;
23
use ApiPlatform\Metadata\GetCollection;
24
use ApiPlatform\Util\ClassInfoTrait;
25
use Doctrine\Common\Util\ClassUtils;
26
use Doctrine\ORM\EntityManagerInterface;
27
use Doctrine\ORM\Event\OnFlushEventArgs;
28
use Doctrine\ORM\Event\PreUpdateEventArgs;
29
use Doctrine\ORM\PersistentCollection;
30
use Symfony\Component\PropertyAccess\PropertyAccess;
31
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
32

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

44
    public function __construct(private readonly PurgerInterface $purger, private readonly IriConverterInterface $iriConverter, private readonly ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null)
45
    {
46
        $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
×
47
    }
48

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

57
        $changeSet = $eventArgs->getEntityChangeSet();
×
58
        $associationMappings = $eventArgs->getEntityManager()->getClassMetadata(ClassUtils::getClass($eventArgs->getObject()))->getAssociationMappings();
×
59

60
        foreach ($changeSet as $key => $value) {
×
61
            if (!isset($associationMappings[$key])) {
×
62
                continue;
×
63
            }
64

65
            $this->addTagsFor($value[0]);
×
66
            $this->addTagsFor($value[1]);
×
67
        }
68
    }
69

70
    /**
71
     * Collects tags from inserted and deleted entities, including relations.
72
     */
73
    public function onFlush(OnFlushEventArgs $eventArgs): void
74
    {
75
        $em = $eventArgs->getEntityManager();
×
76
        $uow = $em->getUnitOfWork();
×
77

78
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
×
79
            $this->gatherResourceAndItemTags($entity, false);
×
80
            $this->gatherRelationTags($em, $entity);
×
81
        }
82

83
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
×
84
            $this->gatherResourceAndItemTags($entity, true);
×
85
            $this->gatherRelationTags($em, $entity);
×
86
        }
87

88
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
×
89
            $this->gatherResourceAndItemTags($entity, true);
×
90
            $this->gatherRelationTags($em, $entity);
×
91
        }
92
    }
93

94
    /**
95
     * Purges tags collected during this request, and clears the tag list.
96
     */
97
    public function postFlush(): void
98
    {
99
        if (empty($this->tags)) {
×
100
            return;
×
101
        }
102

103
        $this->purger->purge(array_values($this->tags));
×
104

105
        $this->tags = [];
×
106
    }
107

108
    private function gatherResourceAndItemTags(object $entity, bool $purgeItem): void
109
    {
110
        try {
×
111
            $resourceClass = $this->resourceClassResolver->getResourceClass($entity);
×
112
            $iri = $this->iriConverter->getIriFromResource($resourceClass, UrlGeneratorInterface::ABS_PATH, new GetCollection());
×
113
            $this->tags[$iri] = $iri;
×
114

115
            if ($purgeItem) {
×
116
                $this->addTagForItem($entity);
×
117
            }
118
        } catch (OperationNotFoundException|InvalidArgumentException) {
×
119
        }
120
    }
121

122
    private function gatherRelationTags(EntityManagerInterface $em, object $entity): void
123
    {
124
        $associationMappings = $em->getClassMetadata(ClassUtils::getClass($entity))->getAssociationMappings();
×
125
        foreach (array_keys($associationMappings) as $property) {
×
126
            if ($this->propertyAccessor->isReadable($entity, $property)) {
×
127
                $this->addTagsFor($this->propertyAccessor->getValue($entity, $property));
×
128
            }
129
        }
130
    }
131

132
    private function addTagsFor(mixed $value): void
133
    {
134
        if (!$value || \is_scalar($value)) {
×
135
            return;
×
136
        }
137

138
        if (!is_iterable($value)) {
×
139
            $this->addTagForItem($value);
×
140

141
            return;
×
142
        }
143

144
        if ($value instanceof PersistentCollection) {
×
145
            $value = clone $value;
×
146
        }
147

148
        foreach ($value as $v) {
×
149
            $this->addTagForItem($v);
×
150
        }
151
    }
152

153
    private function addTagForItem(mixed $value): void
154
    {
155
        if (!$this->resourceClassResolver->isResourceClass($this->getObjectClass($value))) {
×
156
            return;
×
157
        }
158

159
        try {
×
160
            $iri = $this->iriConverter->getIriFromResource($value);
×
161
            $this->tags[$iri] = $iri;
×
162
        } catch (RuntimeException|InvalidArgumentException) {
×
163
        }
164
    }
165
}
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