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

api-platform / core / 5673783280

pending completion
5673783280

push

github

web-flow
test: compatibility with PHPUnit 10 (#5701)

* test: compatibility with PHPUnit 10

* fix

* another fix

* one more fix

* fix

* try again

* again

* fixed locally

* clean composer.json

10 of 10 new or added lines in 2 files covered. (100.0%)

10861 of 18369 relevant lines covered (59.13%)

19.88 hits per line

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

96.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\Metadata\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();
20✔
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();
4✔
55
        $this->gatherResourceAndItemTags($object, true);
4✔
56

57
        $changeSet = $eventArgs->getEntityChangeSet();
4✔
58
        $objectManager = method_exists($eventArgs, 'getObjectManager') ? $eventArgs->getObjectManager() : $eventArgs->getEntityManager();
4✔
59
        $associationMappings = $objectManager->getClassMetadata(ClassUtils::getClass($eventArgs->getObject()))->getAssociationMappings();
4✔
60

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

66
            $this->addTagsFor($value[0]);
2✔
67
            $this->addTagsFor($value[1]);
2✔
68
        }
69
    }
70

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

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

84
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
16✔
85
            $this->gatherResourceAndItemTags($entity, true);
2✔
86
            $this->gatherRelationTags($em, $entity);
2✔
87
        }
88

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

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

104
        $this->purger->purge(array_values($this->tags));
16✔
105

106
        $this->tags = [];
16✔
107
    }
108

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

116
            if ($purgeItem) {
18✔
117
                $this->addTagForItem($entity);
18✔
118
            }
119
        } catch (OperationNotFoundException|InvalidArgumentException) {
4✔
120
        }
121
    }
122

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

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

139
        if (!is_iterable($value)) {
12✔
140
            $this->addTagForItem($value);
4✔
141

142
            return;
4✔
143
        }
144

145
        if ($value instanceof PersistentCollection) {
8✔
146
            $value = clone $value;
8✔
147
        }
148

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

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

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