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

api-platform / core / 15255731762

26 May 2025 01:55PM UTC coverage: 0.0% (-26.5%) from 26.526%
15255731762

Pull #7176

github

web-flow
Merge 66f6cf4d2 into 79edced67
Pull Request #7176: Merge 4.1

0 of 387 new or added lines in 28 files covered. (0.0%)

11394 existing lines in 372 files now uncovered.

0 of 51334 relevant lines covered (0.0%)

0.0 hits per line

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

0.0
/src/Symfony/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\Symfony\Doctrine\EventListener;
15

16
use ApiPlatform\HttpCache\PurgerInterface;
17
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
18
use ApiPlatform\Metadata\Exception\OperationNotFoundException;
19
use ApiPlatform\Metadata\Exception\RuntimeException;
20
use ApiPlatform\Metadata\GetCollection;
21
use ApiPlatform\Metadata\IriConverterInterface;
22
use ApiPlatform\Metadata\ResourceClassResolverInterface;
23
use ApiPlatform\Metadata\UrlGeneratorInterface;
24
use ApiPlatform\Metadata\Util\ClassInfoTrait;
25
use Doctrine\ORM\EntityManagerInterface;
26
use Doctrine\ORM\Event\OnFlushEventArgs;
27
use Doctrine\ORM\Event\PreUpdateEventArgs;
28
use Doctrine\ORM\Mapping\AssociationMapping;
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
    {
UNCOV
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
        // @phpstan-ignore-next-line
59
        $objectManager = method_exists($eventArgs, 'getObjectManager') ? $eventArgs->getObjectManager() : $eventArgs->getEntityManager();
×
60
        $associationMappings = $objectManager->getClassMetadata(\get_class($eventArgs->getObject()))->getAssociationMappings();
×
61

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

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

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

UNCOV
81
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
×
UNCOV
82
            $this->gatherResourceAndItemTags($entity, false);
×
UNCOV
83
            $this->gatherRelationTags($em, $entity);
×
84
        }
85

UNCOV
86
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
×
87
            $this->gatherResourceAndItemTags($entity, true);
×
88
            $this->gatherRelationTags($em, $entity);
×
89
        }
90

UNCOV
91
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
×
92
            $this->gatherResourceAndItemTags($entity, true);
×
93
            $this->gatherRelationTags($em, $entity);
×
94
        }
95
    }
96

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

UNCOV
106
        $this->purger->purge(array_values($this->tags));
×
107

UNCOV
108
        $this->tags = [];
×
109
    }
110

111
    private function gatherResourceAndItemTags(object $entity, bool $purgeItem): void
112
    {
113
        try {
NEW
114
            $iri = $this->iriConverter->getIriFromResource($entity, UrlGeneratorInterface::ABS_PATH, new GetCollection());
×
UNCOV
115
            $this->tags[$iri] = $iri;
×
116

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

124
    private function gatherRelationTags(EntityManagerInterface $em, object $entity): void
125
    {
UNCOV
126
        $associationMappings = $em->getClassMetadata($entity::class)->getAssociationMappings();
×
127
        /** @var array|AssociationMapping $associationMapping according to the version of doctrine orm */
UNCOV
128
        foreach ($associationMappings as $property => $associationMapping) {
×
UNCOV
129
            if ($associationMapping instanceof AssociationMapping && ($associationMapping->targetEntity ?? null) && !$this->resourceClassResolver->isResourceClass($associationMapping->targetEntity)) {
×
UNCOV
130
                return;
×
131
            }
132

133
            if (
UNCOV
134
                \is_array($associationMapping)
×
UNCOV
135
                && \array_key_exists('targetEntity', $associationMapping)
×
UNCOV
136
                && !$this->resourceClassResolver->isResourceClass($associationMapping['targetEntity'])) {
×
137
                return;
×
138
            }
139

UNCOV
140
            if ($this->propertyAccessor->isReadable($entity, $property)) {
×
UNCOV
141
                $this->addTagsFor($this->propertyAccessor->getValue($entity, $property));
×
142
            }
143
        }
144
    }
145

146
    private function addTagsFor(mixed $value): void
147
    {
UNCOV
148
        if (!$value || \is_scalar($value)) {
×
UNCOV
149
            return;
×
150
        }
151

UNCOV
152
        if (!is_iterable($value)) {
×
UNCOV
153
            $this->addTagForItem($value);
×
154

UNCOV
155
            return;
×
156
        }
157

UNCOV
158
        if ($value instanceof PersistentCollection) {
×
UNCOV
159
            $value = clone $value;
×
160
        }
161

UNCOV
162
        foreach ($value as $v) {
×
UNCOV
163
            $this->addTagForItem($v);
×
164
        }
165
    }
166

167
    private function addTagForItem(mixed $value): void
168
    {
UNCOV
169
        if (!$this->resourceClassResolver->isResourceClass($this->getObjectClass($value))) {
×
170
            return;
×
171
        }
172

173
        try {
UNCOV
174
            $iri = $this->iriConverter->getIriFromResource($value);
×
UNCOV
175
            $this->tags[$iri] = $iri;
×
UNCOV
176
        } catch (RuntimeException|InvalidArgumentException) {
×
177
        }
178
    }
179
}
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