Skip to content

Commit e5f1be0

Browse files
toriqotoriqoalanpoulain
authored
feat: add @type property on mercure delete update (#2688)
Co-authored-by: toriqo <flavius@pionix.ro> Co-authored-by: Alan Poulain <contact@alanpoulain.eu>
1 parent 3a845f1 commit e5f1be0

File tree

8 files changed

+41
-10
lines changed

8 files changed

+41
-10
lines changed

src/Doctrine/EventListener/PublishMercureUpdatesListener.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use ApiPlatform\Exception\RuntimeException;
2222
use ApiPlatform\GraphQl\Subscription\MercureSubscriptionIriGeneratorInterface as GraphQlMercureSubscriptionIriGeneratorInterface;
2323
use ApiPlatform\GraphQl\Subscription\SubscriptionManagerInterface as GraphQlSubscriptionManagerInterface;
24+
use ApiPlatform\Metadata\HttpOperation;
2425
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
2526
use ApiPlatform\Symfony\Messenger\DispatchTrait;
2627
use ApiPlatform\Util\ResourceClassInfoTrait;
@@ -63,7 +64,7 @@ final class PublishMercureUpdatesListener
6364
/**
6465
* @param array<string, string[]|string> $formats
6566
*/
66-
public function __construct(ResourceClassResolverInterface $resourceClassResolver, private readonly IriConverterInterface $iriConverter, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly SerializerInterface $serializer, private readonly array $formats, MessageBusInterface $messageBus = null, private readonly ?HubRegistry $hubRegistry = null, private readonly ?GraphQlSubscriptionManagerInterface $graphQlSubscriptionManager = null, private readonly ?GraphQlMercureSubscriptionIriGeneratorInterface $graphQlMercureSubscriptionIriGenerator = null, ExpressionLanguage $expressionLanguage = null)
67+
public function __construct(ResourceClassResolverInterface $resourceClassResolver, private readonly IriConverterInterface $iriConverter, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, private readonly SerializerInterface $serializer, private readonly array $formats, MessageBusInterface $messageBus = null, private readonly ?HubRegistry $hubRegistry = null, private readonly ?GraphQlSubscriptionManagerInterface $graphQlSubscriptionManager = null, private readonly ?GraphQlMercureSubscriptionIriGeneratorInterface $graphQlMercureSubscriptionIriGenerator = null, ExpressionLanguage $expressionLanguage = null, private bool $includeType = false)
6768
{
6869
if (null === $messageBus && null === $hubRegistry) {
6970
throw new InvalidArgumentException('A message bus or a hub registry must be provided.');
@@ -84,6 +85,10 @@ public function __construct(ResourceClassResolverInterface $resourceClassResolve
8485
new ExpressionFunction('iri', static fn (string $apiResource, int $referenceType = UrlGeneratorInterface::ABS_URL): string => sprintf('iri(%s, %d)', $apiResource, $referenceType), static fn (array $arguments, $apiResource, int $referenceType = UrlGeneratorInterface::ABS_URL): string => $iriConverter->getIriFromResource($apiResource, $referenceType))
8586
);
8687
}
88+
89+
if (false === $this->includeType) {
90+
trigger_deprecation('api-platform/core', '3.1', 'Having mercure.include_type (always include @type in Mercure updates, even delete ones) set to false in the configuration is deprecated. It will be true by default in API Platform 4.0.');
91+
}
8792
}
8893

8994
/**
@@ -150,8 +155,9 @@ private function storeObjectToPublish(object $object, string $property): void
150155
return;
151156
}
152157

158+
$operation = $this->resourceMetadataFactory->create($resourceClass)->getOperation();
153159
try {
154-
$options = $this->resourceMetadataFactory->create($resourceClass)->getOperation()->getMercure() ?? false;
160+
$options = $operation->getMercure() ?? false;
155161
} catch (OperationNotFoundException) {
156162
return;
157163
}
@@ -208,9 +214,15 @@ private function storeObjectToPublish(object $object, string $property): void
208214
}
209215

210216
if ('deletedObjects' === $property) {
217+
$types = $operation instanceof HttpOperation ? $operation->getTypes() : null;
218+
if (null === $types) {
219+
$types = [$operation->getShortName()];
220+
}
221+
211222
$this->deletedObjects[(object) [
212223
'id' => $this->iriConverter->getIriFromResource($object),
213224
'iri' => $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_URL),
225+
'type' => 1 === \count($types) ? $types[0] : $types,
214226
]] = $options;
215227

216228
return;
@@ -222,12 +234,12 @@ private function storeObjectToPublish(object $object, string $property): void
222234
private function publishUpdate(object $object, array $options, string $type): void
223235
{
224236
if ($object instanceof \stdClass) {
225-
// By convention, if the object has been deleted, we send only its IRI.
237+
// By convention, if the object has been deleted, we send only its IRI and its type.
226238
// This may change in the feature, because it's not JSON Merge Patch compliant,
227239
// and I'm not a fond of this approach.
228240
$iri = $options['topics'] ?? $object->iri;
229241
/** @var string $data */
230-
$data = json_encode(['@id' => $object->id], \JSON_THROW_ON_ERROR);
242+
$data = json_encode(['@id' => $object->id] + ($this->includeType ? ['@type' => $object->type] : []), \JSON_THROW_ON_ERROR);
231243
} else {
232244
$resourceClass = $this->getObjectClass($object);
233245
$context = $options['normalization_context'] ?? $this->resourceMetadataFactory->create($resourceClass)->getOperation()->getNormalizationContext() ?? [];

src/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,8 @@ private function registerMercureConfiguration(ContainerBuilder $container, array
680680
return;
681681
}
682682

683+
$container->setParameter('api_platform.mercure.include_type', $config['mercure']['include_type']);
684+
683685
$loader->load('mercure.xml');
684686

685687
if ($this->isConfigEnabled($container, $config['doctrine'])) {

src/Symfony/Bundle/DependencyInjection/Configuration.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,10 @@ private function addMercureSection(ArrayNodeDefinition $rootNode): void
376376
->defaultNull()
377377
->info('The URL sent in the Link HTTP header. If not set, will default to the URL for MercureBundle\'s default hub.')
378378
->end()
379+
->booleanNode('include_type')
380+
->defaultFalse()
381+
->info('Always include @type in updates (including delete ones).')
382+
->end()
379383
->end()
380384
->end()
381385
->end();

src/Symfony/Bundle/Resources/config/doctrine_odm_mercure_publisher.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
<argument type="service" id="Symfony\Component\Mercure\HubRegistry" />
1919
<argument type="service" id="api_platform.graphql.subscription.subscription_manager" on-invalid="ignore" />
2020
<argument type="service" id="api_platform.graphql.subscription.mercure_iri_generator" on-invalid="ignore" />
21+
<argument>null</argument>
22+
<argument>%api_platform.mercure.include_type%</argument>
2123

2224
<tag name="doctrine_mongodb.odm.event_listener" event="onFlush" />
2325
<tag name="doctrine_mongodb.odm.event_listener" event="postFlush" />

src/Symfony/Bundle/Resources/config/doctrine_orm_mercure_publisher.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
<argument type="service" id="Symfony\Component\Mercure\HubRegistry" />
1919
<argument type="service" id="api_platform.graphql.subscription.subscription_manager" on-invalid="ignore" />
2020
<argument type="service" id="api_platform.graphql.subscription.mercure_iri_generator" on-invalid="ignore" />
21+
<argument>null</argument>
22+
<argument>%api_platform.mercure.include_type%</argument>
2123

2224
<tag name="doctrine.event_listener" event="onFlush" />
2325
<tag name="doctrine.event_listener" event="postFlush" />

tests/Doctrine/EventListener/PublishMercureUpdatesListenerTest.php

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,16 @@ public function testPublishUpdate(): void
9898
]))]));
9999

100100
$resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadataCollection(Dummy::class, [(new ApiResource())->withOperations(new Operations([
101-
'get' => (new Get())->withMercure(['hub' => 'managed', 'enable_async_update' => false])->withNormalizationContext(['groups' => ['foo', 'bar']]),
101+
'get' => (new Get())->withShortName('Dummy')->withMercure(['hub' => 'managed', 'enable_async_update' => false])->withNormalizationContext(['groups' => ['foo', 'bar']]),
102102
]))]));
103103
$resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadataCollection(DummyCar::class, [(new ApiResource())->withOperations(new Operations([
104104
'get' => new Get(),
105105
]))]));
106106
$resourceMetadataFactoryProphecy->create(DummyFriend::class)->willReturn(new ResourceMetadataCollection(DummyFriend::class, [(new ApiResource())->withOperations(new Operations([
107-
'get' => (new Get())->withMercure(['private' => true, 'retry' => 10, 'hub' => 'managed', 'enable_async_update' => false]),
107+
'get' => (new Get())->withTypes('https://schema.org/Person')->withShortName('DummyFriend')->withMercure(['private' => true, 'retry' => 10, 'hub' => 'managed', 'enable_async_update' => false]),
108108
]))]));
109109
$resourceMetadataFactoryProphecy->create(DummyOffer::class)->willReturn(new ResourceMetadataCollection(DummyOffer::class, [(new ApiResource())->withOperations(new Operations([
110-
'get' => (new Get())->withMercure(['topics' => 'http://example.com/custom_topics/1', 'data' => 'mercure_custom_data', 'hub' => 'managed', 'enable_async_update' => false])->withNormalizationContext(['groups' => ['baz']]),
110+
'get' => (new Get())->withShortName('DummyOffer')->withMercure(['topics' => 'http://example.com/custom_topics/1', 'data' => 'mercure_custom_data', 'hub' => 'managed', 'enable_async_update' => false])->withNormalizationContext(['groups' => ['baz']]),
111111
]))]));
112112
$resourceMetadataFactoryProphecy->create(DummyMercure::class)->willReturn(new ResourceMetadataCollection(DummyMercure::class, [(new ApiResource())->withOperations(new Operations([
113113
'get' => (new Get())->withMercure(['topics' => ['/dummies/1', '/users/3'], 'hub' => 'managed', 'enable_async_update' => false])->withNormalizationContext(['groups' => ['baz']]),
@@ -144,7 +144,11 @@ public function testPublishUpdate(): void
144144
null,
145145
new HubRegistry($this->createMock(HubInterface::class), [
146146
'managed' => $managedHub,
147-
])
147+
]),
148+
null,
149+
null,
150+
null,
151+
true,
148152
);
149153

150154
$uowProphecy = $this->prophesize(UnitOfWork::class);
@@ -159,7 +163,7 @@ public function testPublishUpdate(): void
159163
$listener->onFlush($eventArgs);
160164
$listener->postFlush();
161165

162-
$this->assertEquals(['1', '2', 'mercure_custom_data', 'mercure_options', '{"@id":"\/dummies\/3"}', '{"@id":"\/dummy_friends\/4"}', '{"@id":"\/dummy_offers\/5"}'], $data);
166+
$this->assertEquals(['1', '2', 'mercure_custom_data', 'mercure_options', '{"@id":"\/dummies\/3","@type":"Dummy"}', '{"@id":"\/dummy_friends\/4","@type":"https:\/\/schema.org\/Person"}', '{"@id":"\/dummy_offers\/5","@type":"DummyOffer"}'], $data);
163167
$this->assertEquals(['http://example.com/dummies/1', 'http://example.com/dummies/2', 'http://example.com/custom_topics/1', '/dummies/1', '/users/3', 'http://example.com/dummies/3', 'http://example.com/dummy_friends/4', 'http://example.com/custom_topics/1'], $topics);
164168
$this->assertEquals([false, false, false, false, false, true, false], $private);
165169
$this->assertEquals([null, null, null, null, null, 10, null], $retry);
@@ -218,7 +222,9 @@ public function testPublishGraphQlUpdates(): void
218222
null,
219223
new HubRegistry($defaultHub, ['default' => $defaultHub]),
220224
$graphQlSubscriptionManagerProphecy->reveal(),
221-
$graphQlMercureSubscriptionIriGenerator->reveal()
225+
$graphQlMercureSubscriptionIriGenerator->reveal(),
226+
null,
227+
true,
222228
);
223229

224230
$uowProphecy = $this->prophesize(UnitOfWork::class);

tests/Fixtures/app/config/config_common.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ api_platform:
8080
paths:
8181
- '%kernel.project_dir%/../TestBundle/Enum'
8282
- '%kernel.project_dir%/../TestBundle/Model'
83+
mercure:
84+
include_type: true
8385

8486
parameters:
8587
container.autowiring.strict_mode: true

tests/Symfony/Bundle/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ private function runDefaultConfigTests(array $doctrineIntegrationsToLoad = ['orm
197197
'mercure' => [
198198
'enabled' => true,
199199
'hub_url' => null,
200+
'include_type' => false,
200201
],
201202
'resource_class_directories' => [],
202203
'asset_package' => null,

0 commit comments

Comments
 (0)