1616use ApiPlatform \HttpCache \PurgerInterface ;
1717use ApiPlatform \Metadata \Exception \InvalidArgumentException ;
1818use ApiPlatform \Metadata \Exception \OperationNotFoundException ;
19- use ApiPlatform \Metadata \Exception \RuntimeException ;
2019use ApiPlatform \Metadata \GetCollection ;
2120use ApiPlatform \Metadata \IriConverterInterface ;
2221use ApiPlatform \Metadata \ResourceClassResolverInterface ;
2726use Doctrine \ORM \Event \PreUpdateEventArgs ;
2827use Doctrine \ORM \Mapping \AssociationMapping ;
2928use Doctrine \ORM \PersistentCollection ;
29+ use Symfony \Component \ObjectMapper \Attribute \Map ;
30+ use Symfony \Component \ObjectMapper \ObjectMapperInterface ;
3031use Symfony \Component \PropertyAccess \PropertyAccess ;
3132use Symfony \Component \PropertyAccess \PropertyAccessorInterface ;
3233
@@ -41,7 +42,11 @@ final class PurgeHttpCacheListener
4142 private readonly PropertyAccessorInterface $ propertyAccessor ;
4243 private array $ tags = [];
4344
44- public function __construct (private readonly PurgerInterface $ purger , private readonly IriConverterInterface $ iriConverter , private readonly ResourceClassResolverInterface $ resourceClassResolver , ?PropertyAccessorInterface $ propertyAccessor = null )
45+ public function __construct (private readonly PurgerInterface $ purger ,
46+ private readonly IriConverterInterface $ iriConverter ,
47+ private readonly ResourceClassResolverInterface $ resourceClassResolver ,
48+ ?PropertyAccessorInterface $ propertyAccessor = null ,
49+ private readonly ?ObjectMapperInterface $ objectMapper = null )
4550 {
4651 $ this ->propertyAccessor = $ propertyAccessor ?? PropertyAccess::createPropertyAccessor ();
4752 }
@@ -110,36 +115,47 @@ public function postFlush(): void
110115
111116 private function gatherResourceAndItemTags (object $ entity , bool $ purgeItem ): void
112117 {
113- try {
114- $ iri = $ this ->iriConverter ->getIriFromResource ($ entity , UrlGeneratorInterface::ABS_PATH , new GetCollection ());
115- $ this ->tags [$ iri ] = $ iri ;
118+ $ resources = $ this ->getResourcesForEntity ($ entity );
116119
117- if ($ purgeItem ) {
118- $ this ->addTagForItem ($ entity );
120+ foreach ($ resources as $ resource ) {
121+ try {
122+ $ iri = $ this ->iriConverter ->getIriFromResource ($ resource , UrlGeneratorInterface::ABS_PATH , new GetCollection ());
123+ $ this ->tags [$ iri ] = $ iri ;
124+
125+ if ($ purgeItem ) {
126+ $ this ->addTagForItem ($ entity );
127+ }
128+ } catch (OperationNotFoundException |InvalidArgumentException ) {
119129 }
120- } catch (OperationNotFoundException |InvalidArgumentException ) {
121130 }
122131 }
123132
124133 private function gatherRelationTags (EntityManagerInterface $ em , object $ entity ): void
125134 {
126135 $ associationMappings = $ em ->getClassMetadata ($ entity ::class)->getAssociationMappings ();
136+
127137 /** @var array|AssociationMapping $associationMapping according to the version of doctrine orm */
128138 foreach ($ associationMappings as $ property => $ associationMapping ) {
129139 if ($ associationMapping instanceof AssociationMapping && ($ associationMapping ->targetEntity ?? null ) && !$ this ->resourceClassResolver ->isResourceClass ($ associationMapping ->targetEntity )) {
130140 return ;
131141 }
142+ if (!$ this ->propertyAccessor ->isReadable ($ entity , $ property )) {
143+ return ;
144+ }
132145
133146 if (
134147 \is_array ($ associationMapping )
135148 && \array_key_exists ('targetEntity ' , $ associationMapping )
136- && !$ this ->resourceClassResolver ->isResourceClass ($ associationMapping ['targetEntity ' ])) {
149+ && !$ this ->resourceClassResolver ->isResourceClass ($ associationMapping ['targetEntity ' ])
150+ && (
151+ !$ this ->objectMapper
152+ || !(new \ReflectionClass ($ associationMapping ['targetEntity ' ]))->getAttributes (Map::class)
153+ )
154+ ) {
137155 return ;
138156 }
139157
140- if ($ this ->propertyAccessor ->isReadable ($ entity , $ property )) {
141- $ this ->addTagsFor ($ this ->propertyAccessor ->getValue ($ entity , $ property ));
142- }
158+ $ this ->addTagsFor ($ this ->propertyAccessor ->getValue ($ entity , $ property ));
143159 }
144160 }
145161
@@ -166,14 +182,42 @@ private function addTagsFor(mixed $value): void
166182
167183 private function addTagForItem (mixed $ value ): void
168184 {
169- if (!$ this ->resourceClassResolver ->isResourceClass ($ this ->getObjectClass ($ value ))) {
170- return ;
185+ $ resources = $ this ->getResourcesForEntity ($ value );
186+
187+ foreach ($ resources as $ resource ) {
188+ try {
189+ $ iri = $ this ->iriConverter ->getIriFromResource ($ resource );
190+ $ this ->tags [$ iri ] = $ iri ;
191+ } catch (OperationNotFoundException |InvalidArgumentException ) {
192+ }
171193 }
194+ }
172195
173- try {
174- $ iri = $ this ->iriConverter ->getIriFromResource ($ value );
175- $ this ->tags [$ iri ] = $ iri ;
176- } catch (RuntimeException |InvalidArgumentException ) {
196+ private function getResourcesForEntity (object $ entity ): array
197+ {
198+ $ resources = [];
199+
200+ if (!$ this ->resourceClassResolver ->isResourceClass ($ class = $ this ->getObjectClass ($ entity ))) {
201+ // is the entity mapped to resource(s)?
202+ if (!$ this ->objectMapper ) {
203+ return [];
204+ }
205+
206+ $ mapAttributes = (new \ReflectionClass ($ class ))->getAttributes (Map::class);
207+
208+ if (!$ mapAttributes ) {
209+ return [];
210+ }
211+
212+ // loop over all mappings to fetch all resources mapped to this entity
213+ $ resources = array_map (
214+ fn ($ mapAttribute ) => $ this ->objectMapper ->map ($ entity , $ mapAttribute ->newInstance ()->target ),
215+ $ mapAttributes
216+ );
217+ } else {
218+ $ resources [] = $ entity ;
177219 }
220+
221+ return $ resources ;
178222 }
179223}
0 commit comments