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

api-platform / core / 7142557150

08 Dec 2023 02:28PM UTC coverage: 36.003% (-1.4%) from 37.36%
7142557150

push

github

web-flow
fix(jsonld): remove link to ApiDocumentation when doc is disabled (#6029)

0 of 1 new or added line in 1 file covered. (0.0%)

2297 existing lines in 182 files now uncovered.

9992 of 27753 relevant lines covered (36.0%)

147.09 hits per line

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

98.11
/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();
1,287✔
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();
168✔
57
        $this->gatherResourceAndItemTags($object, true);
168✔
58

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

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

68
            $this->addTagsFor($value[0]);
54✔
69
            $this->addTagsFor($value[1]);
54✔
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();
1,287✔
79
        $uow = $em->getUnitOfWork();
1,287✔
80

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

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

91
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
1,287✔
92
            $this->gatherResourceAndItemTags($entity, true);
42✔
93
            $this->gatherRelationTags($em, $entity);
42✔
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)) {
1,287✔
103
            return;
60✔
104
        }
105

106
        $this->purger->purge(array_values($this->tags));
1,239✔
107

108
        $this->tags = [];
1,239✔
109
    }
110

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

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

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

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

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

149
            return;
462✔
150
        }
151

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

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

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

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

© 2026 Coveralls, Inc