1616use ApiPlatform \Doctrine \Common \Messenger \DispatchTrait ;
1717use ApiPlatform \GraphQl \Subscription \MercureSubscriptionIriGeneratorInterface as GraphQlMercureSubscriptionIriGeneratorInterface ;
1818use ApiPlatform \GraphQl \Subscription \SubscriptionManagerInterface as GraphQlSubscriptionManagerInterface ;
19+ use ApiPlatform \Metadata \CollectionOperationInterface ;
1920use ApiPlatform \Metadata \Exception \InvalidArgumentException ;
20- use ApiPlatform \Metadata \Exception \OperationNotFoundException ;
2121use ApiPlatform \Metadata \Exception \RuntimeException ;
2222use ApiPlatform \Metadata \HttpOperation ;
2323use ApiPlatform \Metadata \IriConverterInterface ;
@@ -58,9 +58,12 @@ final class PublishMercureUpdatesListener
5858 'enable_async_update ' => true ,
5959 ];
6060 private readonly ?ExpressionLanguage $ expressionLanguage ;
61- private \SplObjectStorage $ createdObjects ;
62- private \SplObjectStorage $ updatedObjects ;
63- private \SplObjectStorage $ deletedObjects ;
61+ /** @var list<array{object: object, options: array, operation: ?Operation}> */
62+ private array $ createdObjects ;
63+ /** @var list<array{object: object, options: array, operation: ?Operation}> */
64+ private array $ updatedObjects ;
65+ /** @var list<array{object: object, options: array, operation: ?Operation}> */
66+ private array $ deletedObjects ;
6467
6568 /**
6669 * @param array<string, string[]|string> $formats
@@ -127,40 +130,30 @@ public function onFlush(EventArgs $eventArgs): void
127130 public function postFlush (): void
128131 {
129132 try {
130- $ creatingObjects = clone $ this ->createdObjects ;
131- foreach ($ creatingObjects as $ object ) {
132- if ($ this ->createdObjects ->offsetExists ($ object )) {
133- $ this ->createdObjects ->offsetUnset ($ object );
134- }
135- $ this ->publishUpdate ($ object , $ creatingObjects [$ object ], 'create ' );
133+ foreach ($ this ->createdObjects as $ entry ) {
134+ $ this ->publishUpdate ($ entry ['object ' ], $ entry ['options ' ], 'create ' , $ entry ['operation ' ]);
136135 }
136+ $ this ->createdObjects = [];
137137
138- $ updatingObjects = clone $ this ->updatedObjects ;
139- foreach ($ updatingObjects as $ object ) {
140- if ($ this ->updatedObjects ->offsetExists ($ object )) {
141- $ this ->updatedObjects ->offsetUnset ($ object );
142- }
143- $ this ->publishUpdate ($ object , $ updatingObjects [$ object ], 'update ' );
138+ foreach ($ this ->updatedObjects as $ entry ) {
139+ $ this ->publishUpdate ($ entry ['object ' ], $ entry ['options ' ], 'update ' , $ entry ['operation ' ]);
144140 }
141+ $ this ->updatedObjects = [];
145142
146- $ deletingObjects = clone $ this ->deletedObjects ;
147- foreach ($ deletingObjects as $ object ) {
148- $ options = $ this ->deletedObjects [$ object ];
149- if ($ this ->deletedObjects ->offsetExists ($ object )) {
150- $ this ->deletedObjects ->offsetUnset ($ object );
151- }
152- $ this ->publishUpdate ($ object , $ deletingObjects [$ object ], 'delete ' );
143+ foreach ($ this ->deletedObjects as $ entry ) {
144+ $ this ->publishUpdate ($ entry ['object ' ], $ entry ['options ' ], 'delete ' , $ entry ['operation ' ]);
153145 }
146+ $ this ->deletedObjects = [];
154147 } finally {
155148 $ this ->reset ();
156149 }
157150 }
158151
159152 private function reset (): void
160153 {
161- $ this ->createdObjects = new \ SplObjectStorage () ;
162- $ this ->updatedObjects = new \ SplObjectStorage () ;
163- $ this ->deletedObjects = new \ SplObjectStorage () ;
154+ $ this ->createdObjects = [] ;
155+ $ this ->updatedObjects = [] ;
156+ $ this ->deletedObjects = [] ;
164157 }
165158
166159 private function storeObjectToPublish (object $ object , string $ property ): void
@@ -169,63 +162,79 @@ private function storeObjectToPublish(object $object, string $property): void
169162 return ;
170163 }
171164
172- $ operation = $ this ->resourceMetadataFactory ->create ($ resourceClass )->getOperation ();
173- try {
174- $ options = $ operation ->getMercure () ?? false ;
175- } catch (OperationNotFoundException ) {
176- return ;
177- }
165+ $ resourceMetadataCollection = $ this ->resourceMetadataFactory ->create ($ resourceClass );
178166
179- if (\is_string ($ options )) {
180- if (null === $ this ->expressionLanguage ) {
181- throw new RuntimeException ('The Expression Language component is not installed. Try running "composer require symfony/expression-language". ' );
167+ foreach ($ resourceMetadataCollection as $ resourceMetadata ) {
168+ /** @var ?HttpOperation $operation */
169+ $ operation = null ;
170+ foreach ($ resourceMetadata ->getOperations () ?? [] as $ op ) {
171+ if (!$ op instanceof CollectionOperationInterface) {
172+ $ operation = $ op ;
173+ break ;
174+ }
182175 }
183176
184- $ options = $ this ->expressionLanguage ->evaluate ($ options , ['object ' => $ object ]);
185- }
177+ if (null === $ operation ) {
178+ continue ;
179+ }
186180
187- if (false === $ options ) {
188- return ;
189- }
181+ $ options = $ operation ->getMercure () ?? false ;
190182
191- if (true === $ options ) {
192- $ options = [];
193- }
183+ if (\is_string ($ options )) {
184+ if (null === $ this ->expressionLanguage ) {
185+ throw new RuntimeException ('The Expression Language component is not installed. Try running "composer require symfony/expression-language". ' );
186+ }
194187
195- if (!\is_array ($ options )) {
196- throw new InvalidArgumentException (\sprintf ('The value of the "mercure" attribute of the "%s" resource class must be a boolean, an array of options or an expression returning this array, "%s" given. ' , $ resourceClass , \gettype ($ options )));
197- }
188+ $ options = $ this ->expressionLanguage ->evaluate ($ options , ['object ' => $ object ]);
189+ }
198190
199- foreach ($ options as $ key => $ value ) {
200- if (!isset (self ::ALLOWED_KEYS [$ key ])) {
201- throw new InvalidArgumentException (\sprintf ('The option "%s" set in the "mercure" attribute of the "%s" resource does not exist. Existing options: "%s" ' , $ key , $ resourceClass , implode ('", " ' , array_keys (self ::ALLOWED_KEYS ))));
191+ if (false === $ options ) {
192+ continue ;
202193 }
203- }
204194
205- $ options ['enable_async_update ' ] ??= true ;
195+ if (true === $ options ) {
196+ $ options = [];
197+ }
206198
207- if ('deletedObjects ' === $ property ) {
208- $ types = $ operation instanceof HttpOperation ? $ operation ->getTypes () : null ;
209- if (null === $ types ) {
210- $ types = [$ operation ->getShortName ()];
199+ if (!\is_array ($ options )) {
200+ throw new InvalidArgumentException (\sprintf ('The value of the "mercure" attribute of the "%s" resource class must be a boolean, an array of options or an expression returning this array, "%s" given. ' , $ resourceClass , \gettype ($ options )));
211201 }
212202
213- // We need to evaluate it here, because in publishUpdate() the resource would be already deleted
214- $ this ->evaluateTopics ($ options , $ object );
203+ foreach ($ options as $ key => $ value ) {
204+ if (!isset (self ::ALLOWED_KEYS [$ key ])) {
205+ throw new InvalidArgumentException (\sprintf ('The option "%s" set in the "mercure" attribute of the "%s" resource does not exist. Existing options: "%s" ' , $ key , $ resourceClass , implode ('", " ' , array_keys (self ::ALLOWED_KEYS ))));
206+ }
207+ }
215208
216- $ this ->deletedObjects [(object ) [
217- 'id ' => $ this ->iriConverter ->getIriFromResource ($ object ),
218- 'iri ' => $ this ->iriConverter ->getIriFromResource ($ object , UrlGeneratorInterface::ABS_URL ),
219- 'type ' => 1 === \count ($ types ) ? $ types [0 ] : $ types ,
220- ]] = $ options ;
209+ $ options ['enable_async_update ' ] ??= true ;
221210
222- return ;
223- }
211+ if ('deletedObjects ' === $ property ) {
212+ $ types = $ operation ->getTypes ();
213+ if (null === $ types ) {
214+ $ types = [$ operation ->getShortName ()];
215+ }
216+
217+ // We need to evaluate it here, because in publishUpdate() the resource would be already deleted
218+ $ this ->evaluateTopics ($ options , $ object );
219+
220+ $ this ->deletedObjects [] = [
221+ 'object ' => (object ) [
222+ 'id ' => $ this ->iriConverter ->getIriFromResource ($ object , UrlGeneratorInterface::ABS_PATH , $ operation ),
223+ 'iri ' => $ this ->iriConverter ->getIriFromResource ($ object , UrlGeneratorInterface::ABS_URL , $ operation ),
224+ 'type ' => 1 === \count ($ types ) ? $ types [0 ] : $ types ,
225+ ],
226+ 'options ' => $ options ,
227+ 'operation ' => $ operation ,
228+ ];
224229
225- $ this ->{$ property }[$ object ] = $ options ;
230+ continue ;
231+ }
232+
233+ $ this ->{$ property }[] = ['object ' => $ object , 'options ' => $ options , 'operation ' => $ operation ];
234+ }
226235 }
227236
228- private function publishUpdate (object $ object , array $ options , string $ type ): void
237+ private function publishUpdate (object $ object , array $ options , string $ type, ? Operation $ operation = null ): void
229238 {
230239 if ($ object instanceof \stdClass) {
231240 // By convention, if the object has been deleted, we send only its IRI and its type.
@@ -235,13 +244,12 @@ private function publishUpdate(object $object, array $options, string $type): vo
235244 /** @var non-empty-string $data */
236245 $ data = json_encode (['@id ' => $ object ->id ] + ($ this ->includeType ? ['@type ' => $ object ->type ] : []), \JSON_THROW_ON_ERROR );
237246 } else {
238- $ resourceClass = $ this ->getObjectClass ($ object );
239- $ context = $ options ['normalization_context ' ] ?? $ this ->resourceMetadataFactory ->create ($ resourceClass )->getOperation ()->getNormalizationContext () ?? [];
247+ $ context = $ options ['normalization_context ' ] ?? $ operation ?->getNormalizationContext() ?? [];
240248
241249 // We need to evaluate it here, because in storeObjectToPublish() the resource would not have been persisted yet
242250 $ this ->evaluateTopics ($ options , $ object );
243251
244- $ iri = $ options ['topics ' ] ?? $ this ->iriConverter ->getIriFromResource ($ object , UrlGeneratorInterface::ABS_URL );
252+ $ iri = $ options ['topics ' ] ?? $ this ->iriConverter ->getIriFromResource ($ object , UrlGeneratorInterface::ABS_URL , $ operation );
245253 $ data = $ options ['data ' ] ?? $ this ->serializer ->serialize ($ object , key ($ this ->formats ), $ context );
246254 }
247255
0 commit comments