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

api-platform / core / 6665511178

27 Oct 2023 09:37AM UTC coverage: 37.484% (+0.007%) from 37.477%
6665511178

push

github

soyuka
chore: 3.2.2 changelog

10319 of 27529 relevant lines covered (37.48%)

20.61 hits per line

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

96.23
/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\PersistentCollection;
32
use Symfony\Component\PropertyAccess\PropertyAccess;
33
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
34

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

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

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

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

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

68
            $this->addTagsFor($value[0]);
3✔
69
            $this->addTagsFor($value[1]);
3✔
70
        }
71
    }
72

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

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

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

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

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

106
        $this->purger->purge(array_values($this->tags));
24✔
107

108
        $this->tags = [];
24✔
109
    }
110

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

118
            if ($purgeItem) {
27✔
119
                $this->addTagForItem($entity);
27✔
120
            }
121
        } catch (OperationNotFoundException|InvalidArgumentException) {
6✔
122
        }
123
    }
124

125
    private function gatherRelationTags(EntityManagerInterface $em, object $entity): void
126
    {
127
        $associationMappings = $em->getClassMetadata(ClassUtils::getClass($entity))->getAssociationMappings();
24✔
128
        foreach (array_keys($associationMappings) as $property) {
24✔
129
            if (
130
                \array_key_exists('targetEntity', $associationMappings[$property])
18✔
131
                && !$this->resourceClassResolver->isResourceClass($associationMappings[$property]['targetEntity'])) {
18✔
132
                return;
3✔
133
            }
134
            if ($this->propertyAccessor->isReadable($entity, $property)) {
18✔
135
                $this->addTagsFor($this->propertyAccessor->getValue($entity, $property));
18✔
136
            }
137
        }
138
    }
139

140
    private function addTagsFor(mixed $value): void
141
    {
142
        if (!$value || \is_scalar($value)) {
21✔
143
            return;
12✔
144
        }
145

146
        if (!is_iterable($value)) {
18✔
147
            $this->addTagForItem($value);
6✔
148

149
            return;
6✔
150
        }
151

152
        if ($value instanceof PersistentCollection) {
12✔
153
            $value = clone $value;
12✔
154
        }
155

156
        foreach ($value as $v) {
12✔
157
            $this->addTagForItem($v);
×
158
        }
159
    }
160

161
    private function addTagForItem(mixed $value): void
162
    {
163
        if (!$this->resourceClassResolver->isResourceClass($this->getObjectClass($value))) {
9✔
164
            return;
3✔
165
        }
166

167
        try {
168
            $iri = $this->iriConverter->getIriFromResource($value);
6✔
169
            $this->tags[$iri] = $iri;
6✔
170
        } catch (RuntimeException|InvalidArgumentException) {
×
171
        }
172
    }
173
}
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