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

api-platform / core / 10681662455

03 Sep 2024 11:02AM UTC coverage: 70.804% (-1.7%) from 72.507%
10681662455

push

github

soyuka
fix(symfony): register doctrine common event listeners

3075 of 4343 relevant lines covered (70.8%)

75.56 hits per line

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

0.0
/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();
×
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
    {
58
        $object = $eventArgs->getObject();
×
59
        $this->gatherResourceAndItemTags($object, true);
×
60

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

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

71
            $this->addTagsFor($value[0]);
×
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();
×
83
        $uow = $em->getUnitOfWork();
×
84

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

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

95
        foreach ($uow->getScheduledEntityDeletions() as $entity) {
×
96
            $this->gatherResourceAndItemTags($entity, true);
×
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)) {
×
107
            return;
×
108
        }
109

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

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

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

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

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

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

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

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

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

160
            return;
×
161
        }
162

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

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

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

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