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

api-platform / core / 10670547877

02 Sep 2024 04:30PM UTC coverage: 72.524% (-1.8%) from 74.332%
10670547877

push

github

web-flow
fix(doctrine): move event listeners to doctrine/common (#6573)

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

81 existing lines in 4 files now uncovered.

3149 of 4342 relevant lines covered (72.52%)

76.7 hits per line

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

64.29
/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\ORM\EntityManagerInterface;
28
use Doctrine\ORM\Event\OnFlushEventArgs;
29
use Doctrine\ORM\Event\PreUpdateEventArgs;
30
use Doctrine\ORM\Mapping\AssociationMapping;
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
 * @deprecated moved to \ApiPlatform\Doctrine\Common\EventListener\PurgeHttpCacheListener
41
 */
42
final class PurgeHttpCacheListener
43
{
44
    use ClassInfoTrait;
45
    private readonly PropertyAccessorInterface $propertyAccessor;
46
    private array $tags = [];
47

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

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

UNCOV
61
        $changeSet = $eventArgs->getEntityChangeSet();
×
62
        // @phpstan-ignore-next-line
UNCOV
63
        $objectManager = method_exists($eventArgs, 'getObjectManager') ? $eventArgs->getObjectManager() : $eventArgs->getEntityManager();
×
UNCOV
64
        $associationMappings = $objectManager->getClassMetadata(\get_class($eventArgs->getObject()))->getAssociationMappings();
×
65

UNCOV
66
        foreach ($changeSet as $key => $value) {
×
UNCOV
67
            if (!isset($associationMappings[$key])) {
×
UNCOV
68
                continue;
×
69
            }
70

UNCOV
71
            $this->addTagsFor($value[0]);
×
UNCOV
72
            $this->addTagsFor($value[1]);
×
73
        }
74
    }
75

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

85
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
79✔
86
            $this->gatherResourceAndItemTags($entity, false);
79✔
87
            $this->gatherRelationTags($em, $entity);
79✔
88
        }
89

90
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
79✔
UNCOV
91
            $this->gatherResourceAndItemTags($entity, true);
×
UNCOV
92
            $this->gatherRelationTags($em, $entity);
×
93
        }
94

95
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
79✔
UNCOV
96
            $this->gatherResourceAndItemTags($entity, true);
×
UNCOV
97
            $this->gatherRelationTags($em, $entity);
×
98
        }
99
    }
100

101
    /**
102
     * Purges tags collected during this request, and clears the tag list.
103
     */
104
    public function postFlush(): void
105
    {
106
        if (empty($this->tags)) {
79✔
UNCOV
107
            return;
×
108
        }
109

110
        $this->purger->purge(array_values($this->tags));
79✔
111

112
        $this->tags = [];
79✔
113
    }
114

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

122
            if ($purgeItem) {
79✔
123
                $this->addTagForItem($entity);
79✔
124
            }
UNCOV
125
        } catch (OperationNotFoundException|InvalidArgumentException) {
×
126
        }
127
    }
128

129
    private function gatherRelationTags(EntityManagerInterface $em, object $entity): void
130
    {
131
        $associationMappings = $em->getClassMetadata($entity::class)->getAssociationMappings();
79✔
132
        /** @var array|AssociationMapping $associationMapping according to the version of doctrine orm */
133
        foreach ($associationMappings as $property => $associationMapping) {
79✔
134
            if ($associationMapping instanceof AssociationMapping && ($associationMapping->targetEntity ?? null) && !$this->resourceClassResolver->isResourceClass($associationMapping->targetEntity)) {
44✔
135
                return;
8✔
136
            }
137

138
            if (
139
                \is_array($associationMapping)
36✔
140
                && \array_key_exists('targetEntity', $associationMapping)
36✔
141
                && !$this->resourceClassResolver->isResourceClass($associationMapping['targetEntity'])) {
36✔
UNCOV
142
                return;
×
143
            }
144

145
            if ($this->propertyAccessor->isReadable($entity, $property)) {
36✔
146
                $this->addTagsFor($this->propertyAccessor->getValue($entity, $property));
36✔
147
            }
148
        }
149
    }
150

151
    private function addTagsFor(mixed $value): void
152
    {
153
        if (!$value || \is_scalar($value)) {
36✔
154
            return;
12✔
155
        }
156

157
        if (!is_iterable($value)) {
36✔
158
            $this->addTagForItem($value);
12✔
159

160
            return;
12✔
161
        }
162

163
        if ($value instanceof PersistentCollection) {
24✔
164
            $value = clone $value;
24✔
165
        }
166

167
        foreach ($value as $v) {
24✔
168
            $this->addTagForItem($v);
×
169
        }
170
    }
171

172
    private function addTagForItem(mixed $value): void
173
    {
174
        if (!$this->resourceClassResolver->isResourceClass($this->getObjectClass($value))) {
12✔
UNCOV
175
            return;
×
176
        }
177

178
        try {
179
            $iri = $this->iriConverter->getIriFromResource($value);
12✔
UNCOV
180
            $this->tags[$iri] = $iri;
×
181
        } catch (RuntimeException|InvalidArgumentException) {
12✔
182
        }
183
    }
184
}
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