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

api-platform / core / 8154986070

05 Mar 2024 10:46AM UTC coverage: 58.897% (-0.03%) from 58.922%
8154986070

push

github

soyuka
test(symfony): empty collection without fields (#6165)

see https://github.com/symfony/symfony/commit/b34153555

10900 of 18507 relevant lines covered (58.9%)

19.84 hits per line

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

94.64
/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\Mapping\AssociationMapping;
30
use Doctrine\ORM\PersistentCollection;
31
use Symfony\Component\PropertyAccess\PropertyAccess;
32
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
33

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

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

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

58
        $changeSet = $eventArgs->getEntityChangeSet();
4✔
59
        // @phpstan-ignore-next-line
60
        $objectManager = method_exists($eventArgs, 'getObjectManager') ? $eventArgs->getObjectManager() : $eventArgs->getEntityManager();
4✔
61
        $associationMappings = $objectManager->getClassMetadata(ClassUtils::getClass($eventArgs->getObject()))->getAssociationMappings();
4✔
62

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

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

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

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

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

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

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

107
        $this->purger->purge(array_values($this->tags));
16✔
108

109
        $this->tags = [];
16✔
110
    }
111

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

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

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

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

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

148
    private function addTagsFor(mixed $value): void
149
    {
150
        if (!$value || \is_scalar($value)) {
14✔
151
            return;
8✔
152
        }
153

154
        if (!is_iterable($value)) {
12✔
155
            $this->addTagForItem($value);
4✔
156

157
            return;
4✔
158
        }
159

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

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

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

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