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

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

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

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

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

71.43
/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();
115✔
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();
115✔
UNCOV
79
        $uow = $em->getUnitOfWork();
115✔
80

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

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

UNCOV
91
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
115✔
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)) {
115✔
UNCOV
103
            return;
2✔
104
        }
105

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

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

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

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

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

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

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

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

UNCOV
153
        if (!is_iterable($value)) {
13✔
UNCOV
154
            $this->addTagForItem($value);
7✔
155

UNCOV
156
            return;
7✔
157
        }
158

UNCOV
159
        if ($value instanceof PersistentCollection) {
8✔
UNCOV
160
            $value = clone $value;
8✔
161
        }
162

UNCOV
163
        foreach ($value as $v) {
8✔
UNCOV
164
            $this->addTagForItem($v);
2✔
165
        }
166
    }
167

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

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