Skip to content

Commit ef32a22

Browse files
committed
decouple the Webhook component from the Serializer component
1 parent a58cfb3 commit ef32a22

File tree

12 files changed

+168
-22
lines changed

12 files changed

+168
-22
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+5-10
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ public function load(array $configs, ContainerBuilder $container): void
548548
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
549549

550550
if ($this->readConfigEnabled('webhook', $container, $config['webhook'])) {
551-
$this->registerWebhookConfiguration($config['webhook'], $container, $loader);
551+
$this->registerWebhookConfiguration($config['webhook'], $container, $loader, $this->readConfigEnabled('serializer', $container, $config['serializer']));
552552

553553
// If Webhook is installed but the HttpClient or Serializer components are not available, we should throw an error
554554
if (!$this->readConfigEnabled('http_client', $container, $config['http_client'])) {
@@ -559,14 +559,6 @@ public function load(array $configs, ContainerBuilder $container): void
559559
)
560560
->addTag('container.error');
561561
}
562-
if (!$this->readConfigEnabled('serializer', $container, $config['serializer'])) {
563-
$container->getDefinition('webhook.body_configurator.json')
564-
->setArguments([])
565-
->addError('You cannot use the "webhook transport" service since the Serializer component is not '
566-
.(class_exists(Serializer::class) ? 'enabled. Try setting "framework.serializer.enabled" to true.' : 'installed. Try running "composer require symfony/serializer-pack".')
567-
)
568-
->addTag('container.error');
569-
}
570562
}
571563

572564
if ($this->readConfigEnabled('remote-event', $container, $config['remote-event'])) {
@@ -2919,7 +2911,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
29192911
}
29202912
}
29212913

2922-
private function registerWebhookConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
2914+
private function registerWebhookConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader, bool $serializerEnabled): void
29232915
{
29242916
if (!class_exists(WebhookController::class)) {
29252917
throw new LogicException('Webhook support cannot be enabled as the component is not installed. Try running "composer require symfony/webhook".');
@@ -2938,6 +2930,9 @@ private function registerWebhookConfiguration(array $config, ContainerBuilder $c
29382930
$controller = $container->getDefinition('webhook.controller');
29392931
$controller->replaceArgument(0, $parsers);
29402932
$controller->replaceArgument(1, new Reference($config['message_bus']));
2933+
2934+
$jsonBodyConfigurator = $container->getDefinition('webhook.body_configurator.json');
2935+
$jsonBodyConfigurator->replaceArgument(0, new Reference($serializerEnabled ? 'webhook.payload_serializer.serializer' : 'webhook.payload_serializer.json'));
29412936
}
29422937

29432938
private function registerRemoteEventConfiguration(PhpFileLoader $loader): void

src/Symfony/Bundle/FrameworkBundle/Resources/config/webhook.php

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use Symfony\Component\Webhook\Server\HeadersConfigurator;
1818
use Symfony\Component\Webhook\Server\HeaderSignatureConfigurator;
1919
use Symfony\Component\Webhook\Server\JsonBodyConfigurator;
20+
use Symfony\Component\Webhook\Server\NativeJsonPayloadSerializer;
21+
use Symfony\Component\Webhook\Server\SerializerPayloadSerializer;
2022
use Symfony\Component\Webhook\Server\Transport;
2123

2224
return static function (ContainerConfigurator $container) {
@@ -32,6 +34,13 @@
3234
->set('webhook.headers_configurator', HeadersConfigurator::class)
3335

3436
->set('webhook.body_configurator.json', JsonBodyConfigurator::class)
37+
->args([
38+
abstract_arg('payload serializer'),
39+
])
40+
41+
->set('webhook.payload_serializer.json', NativeJsonPayloadSerializer::class)
42+
43+
->set('webhook.payload_serializer.serializer', SerializerPayloadSerializer::class)
3544
->args([
3645
service('serializer'),
3746
])

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@
2727
use Symfony\Component\Messenger\MessageBusInterface;
2828
use Symfony\Component\Notifier\Notifier;
2929
use Symfony\Component\RateLimiter\Policy\TokenBucketLimiter;
30+
use Symfony\Component\RemoteEvent\RemoteEvent;
3031
use Symfony\Component\Scheduler\Messenger\SchedulerTransportFactory;
3132
use Symfony\Component\Serializer\Encoder\JsonDecode;
3233
use Symfony\Component\TypeInfo\Type;
3334
use Symfony\Component\Uid\Factory\UuidFactory;
35+
use Symfony\Component\Webhook\Controller\WebhookController;
3436

3537
class ConfigurationTest extends TestCase
3638
{
@@ -925,12 +927,12 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
925927
],
926928
'exceptions' => [],
927929
'webhook' => [
928-
'enabled' => false,
930+
'enabled' => !class_exists(FullStack::class) && class_exists(WebhookController::class),
929931
'routing' => [],
930932
'message_bus' => 'messenger.default_bus',
931933
],
932934
'remote-event' => [
933-
'enabled' => false,
935+
'enabled' => !class_exists(FullStack::class) && class_exists(RemoteEvent::class),
934936
],
935937
];
936938
}

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php

+2-6
Original file line numberDiff line numberDiff line change
@@ -2357,7 +2357,7 @@ public function testWebhook()
23572357
$this->assertSame(RequestParser::class, $container->getDefinition('webhook.request_parser')->getClass());
23582358

23592359
$this->assertFalse($container->getDefinition('webhook.transport')->hasErrors());
2360-
$this->assertFalse($container->getDefinition('webhook.body_configurator.json')->hasErrors());
2360+
$this->assertEquals('webhook.payload_serializer.serializer', $container->getDefinition('webhook.body_configurator.json')->getArgument(0));
23612361
}
23622362

23632363
public function testWebhookWithoutSerializer()
@@ -2369,11 +2369,7 @@ public function testWebhookWithoutSerializer()
23692369
$container = $this->createContainerFromFile('webhook_without_serializer');
23702370

23712371
$this->assertFalse($container->getDefinition('webhook.transport')->hasErrors());
2372-
$this->assertTrue($container->getDefinition('webhook.body_configurator.json')->hasErrors());
2373-
$this->assertSame(
2374-
['You cannot use the "webhook transport" service since the Serializer component is not enabled. Try setting "framework.serializer.enabled" to true.'],
2375-
$container->getDefinition('webhook.body_configurator.json')->getErrors()
2376-
);
2372+
$this->assertEquals('webhook.payload_serializer.json', $container->getDefinition('webhook.body_configurator.json')->getArgument(0));
23772373
}
23782374

23792375
public function testAssetMapperWithoutAssets()

src/Symfony/Bundle/FrameworkBundle/composer.json

+2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
"symfony/property-info": "^6.4|^7.0",
7272
"symfony/uid": "^6.4|^7.0",
7373
"symfony/web-link": "^6.4|^7.0",
74+
"symfony/webhook": "^7.2",
7475
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
7576
"twig/twig": "^3.0.4"
7677
},
@@ -102,6 +103,7 @@
102103
"symfony/twig-bundle": "<6.4",
103104
"symfony/validator": "<6.4",
104105
"symfony/web-profiler-bundle": "<6.4",
106+
"symfony/webhook": "<7.2",
105107
"symfony/workflow": "<6.4"
106108
},
107109
"autoload": {

src/Symfony/Component/Webhook/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
7.2
5+
---
6+
7+
* Add `PayloadSerializerInterface` with implementations to decouple the remote event handling from the Serializer component
8+
49
6.4
510
---
611

src/Symfony/Component/Webhook/Server/JsonBodyConfigurator.php

+6-4
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@
2020
*/
2121
final class JsonBodyConfigurator implements RequestConfiguratorInterface
2222
{
23-
public function __construct(
24-
private readonly SerializerInterface $serializer,
25-
) {
23+
private PayloadSerializerInterface $payloadSerializer;
24+
25+
public function __construct(SerializerInterface|PayloadSerializerInterface $payloadSerializer)
26+
{
27+
$this->payloadSerializer = $payloadSerializer instanceof SerializerInterface ? new SerializerPayloadSerializer($payloadSerializer) : $payloadSerializer;
2628
}
2729

2830
public function configure(RemoteEvent $event, #[\SensitiveParameter] string $secret, HttpOptions $options): void
2931
{
30-
$body = $this->serializer->serialize($event->getPayload(), 'json');
32+
$body = $this->payloadSerializer->serialize($event->getPayload());
3133
$options->setBody($body);
3234
$headers = $options->toArray()['headers'];
3335
$headers['Content-Type'] = 'application/json';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Webhook\Server;
13+
14+
final class NativeJsonPayloadSerializer implements PayloadSerializerInterface
15+
{
16+
public function serialize(array $payload): string
17+
{
18+
return json_encode($payload, \JSON_THROW_ON_ERROR);
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Webhook\Server;
13+
14+
interface PayloadSerializerInterface
15+
{
16+
public function serialize(array $payload): string;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Webhook\Server;
13+
14+
use Symfony\Component\Serializer\SerializerInterface;
15+
16+
final class SerializerPayloadSerializer implements PayloadSerializerInterface
17+
{
18+
public function __construct(
19+
private SerializerInterface $serializer,
20+
) {
21+
}
22+
23+
public function serialize(array $payload): string
24+
{
25+
return $this->serializer->serialize($payload, 'json');
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.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+
namespace Symfony\Component\Webhook\Tests\Server;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpClient\HttpOptions;
16+
use Symfony\Component\RemoteEvent\RemoteEvent;
17+
use Symfony\Component\Serializer\SerializerInterface;
18+
use Symfony\Component\Webhook\Server\JsonBodyConfigurator;
19+
use Symfony\Component\Webhook\Server\PayloadSerializerInterface;
20+
21+
class JsonBodyConfiguratorTest extends TestCase
22+
{
23+
public function testPayloadWithPayloadSerializer()
24+
{
25+
$payload = ['foo' => 'bar'];
26+
27+
$payloadSerializer = $this->createMock(PayloadSerializerInterface::class);
28+
$payloadSerializer
29+
->expects($this->once())
30+
->method('serialize')
31+
->with($payload)
32+
;
33+
34+
$httpOptions = new HttpOptions();
35+
$httpOptions->setHeaders([
36+
'Webhook-Event' => 'event-name',
37+
'Webhook-Id' => 'event-id',
38+
]);
39+
40+
$configurator = new JsonBodyConfigurator($payloadSerializer);
41+
$configurator->configure(new RemoteEvent('event-name', 'event-id', $payload), 's3cr3t', $httpOptions);
42+
}
43+
44+
public function testPayloadWithSerializer()
45+
{
46+
$payload = ['foo' => 'bar'];
47+
48+
$payloadEncoder = $this->createMock(SerializerInterface::class);
49+
$payloadEncoder
50+
->expects($this->once())
51+
->method('serialize')
52+
->with($payload, 'json')
53+
->willReturn('{"foo": "bar"}')
54+
;
55+
56+
$httpOptions = new HttpOptions();
57+
$httpOptions->setHeaders([
58+
'Webhook-Event' => 'event-name',
59+
'Webhook-Id' => 'event-id',
60+
]);
61+
62+
$configurator = new JsonBodyConfigurator($payloadEncoder);
63+
$configurator->configure(new RemoteEvent('event-name', 'event-id', $payload), 's3cr3t', $httpOptions);
64+
65+
$this->assertJsonStringEqualsJsonString('{"foo": "bar"}', $httpOptions->toArray()['body']);
66+
}
67+
}

src/Symfony/Component/Webhook/composer.json

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
"symfony/messenger": "^6.4|^7.0",
2323
"symfony/remote-event": "^6.4|^7.0"
2424
},
25+
"require-dev": {
26+
"symfony/http-client": "^6.4|^7.0",
27+
"symfony/serializer": "^6.4|^7.0"
28+
},
2529
"autoload": {
2630
"psr-4": { "Symfony\\Component\\Webhook\\": "" },
2731
"exclude-from-classmap": [

0 commit comments

Comments
 (0)