diff --git a/UPGRADE-7.2.md b/UPGRADE-7.2.md index 7c0c8064f4381..21e41530e8b17 100644 --- a/UPGRADE-7.2.md +++ b/UPGRADE-7.2.md @@ -84,6 +84,15 @@ TwigBridge * Deprecate passing a tag to the constructor of `FormThemeNode` +Webhook +------- + + * [BC BREAK] `RequestParserInterface::parse()` return type changed from + `?RemoteEvent` to `RemoteEvent|array|null`. Classes already + implementing this interface are unaffected but consumers of this method + will need to be updated to handle the new return type. Projects relying on + the `WebhookController` of the component are not affected by the BC break + Yaml ---- diff --git a/src/Symfony/Component/Webhook/CHANGELOG.md b/src/Symfony/Component/Webhook/CHANGELOG.md index aaa1e62d24ddb..2cfc1d7d36e25 100644 --- a/src/Symfony/Component/Webhook/CHANGELOG.md +++ b/src/Symfony/Component/Webhook/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Make `AbstractRequestParserTestCase` compatible with PHPUnit 10+ * Add `PayloadSerializerInterface` with implementations to decouple the remote event handling from the Serializer component * Add optional `$request` argument to `RequestParserInterface::createSuccessfulResponse()` and `RequestParserInterface::createRejectedResponse()` + * [BC BREAK] Change return type of `RequestParserInterface::parse()` to `RemoteEvent|array|null` (from `?RemoteEvent`) 6.4 --- diff --git a/src/Symfony/Component/Webhook/Client/AbstractRequestParser.php b/src/Symfony/Component/Webhook/Client/AbstractRequestParser.php index e8bf3f3e00317..227efd1e86783 100644 --- a/src/Symfony/Component/Webhook/Client/AbstractRequestParser.php +++ b/src/Symfony/Component/Webhook/Client/AbstractRequestParser.php @@ -22,7 +22,7 @@ */ abstract class AbstractRequestParser implements RequestParserInterface { - public function parse(Request $request, #[\SensitiveParameter] string $secret): ?RemoteEvent + public function parse(Request $request, #[\SensitiveParameter] string $secret): RemoteEvent|array|null { $this->validate($request); @@ -47,7 +47,7 @@ public function createRejectedResponse(string $reason/* , ?Request $request = nu abstract protected function getRequestMatcher(): RequestMatcherInterface; - abstract protected function doParse(Request $request, #[\SensitiveParameter] string $secret): ?RemoteEvent; + abstract protected function doParse(Request $request, #[\SensitiveParameter] string $secret): RemoteEvent|array|null; protected function validate(Request $request): void { diff --git a/src/Symfony/Component/Webhook/Client/RequestParserInterface.php b/src/Symfony/Component/Webhook/Client/RequestParserInterface.php index cb84f82e595e5..dd6f1632e41c3 100644 --- a/src/Symfony/Component/Webhook/Client/RequestParserInterface.php +++ b/src/Symfony/Component/Webhook/Client/RequestParserInterface.php @@ -24,11 +24,11 @@ interface RequestParserInterface /** * Parses an HTTP Request and converts it into a RemoteEvent. * - * @return ?RemoteEvent Returns null if the webhook must be ignored + * @return RemoteEvent|RemoteEvent[]|null Returns null if the webhook must be ignored * * @throws RejectWebhookException When the payload is rejected (signature issue, parse issue, ...) */ - public function parse(Request $request, #[\SensitiveParameter] string $secret): ?RemoteEvent; + public function parse(Request $request, #[\SensitiveParameter] string $secret): RemoteEvent|array|null; /** * @param Request|null $request The original request that was received by the webhook controller diff --git a/src/Symfony/Component/Webhook/Controller/WebhookController.php b/src/Symfony/Component/Webhook/Controller/WebhookController.php index f46fe2a5f10c8..a5df467fdc162 100644 --- a/src/Symfony/Component/Webhook/Controller/WebhookController.php +++ b/src/Symfony/Component/Webhook/Controller/WebhookController.php @@ -40,12 +40,17 @@ public function handle(string $type, Request $request): Response } /** @var RequestParserInterface $parser */ $parser = $this->parsers[$type]['parser']; + $events = $parser->parse($request, $this->parsers[$type]['secret']); - if (!$event = $parser->parse($request, $this->parsers[$type]['secret'])) { + if (!$events) { return $parser->createRejectedResponse('Unable to parse the webhook payload.', $request); } - $this->bus->dispatch(new ConsumeRemoteEventMessage($type, $event)); + $events = \is_array($events) ? $events : [$events]; + + foreach ($events as $event) { + $this->bus->dispatch(new ConsumeRemoteEventMessage($type, $event)); + } return $parser->createSuccessfulResponse($request); } diff --git a/src/Symfony/Component/Webhook/Test/AbstractRequestParserTestCase.php b/src/Symfony/Component/Webhook/Test/AbstractRequestParserTestCase.php index 3afcd76624e70..eadf2ac72b70c 100644 --- a/src/Symfony/Component/Webhook/Test/AbstractRequestParserTestCase.php +++ b/src/Symfony/Component/Webhook/Test/AbstractRequestParserTestCase.php @@ -26,7 +26,7 @@ abstract class AbstractRequestParserTestCase extends TestCase * @dataProvider getPayloads */ #[DataProvider('getPayloads')] - public function testParse(string $payload, RemoteEvent $expected) + public function testParse(string $payload, RemoteEvent|array $expected) { $request = $this->createRequest($payload); $parser = $this->createRequestParser(); @@ -35,7 +35,7 @@ public function testParse(string $payload, RemoteEvent $expected) } /** - * @return iterable + * @return iterable */ public static function getPayloads(): iterable { diff --git a/src/Symfony/Component/Webhook/Tests/Controller/WebhookControllerTest.php b/src/Symfony/Component/Webhook/Tests/Controller/WebhookControllerTest.php index b97f7092d9e65..1a3d5196e1e5b 100644 --- a/src/Symfony/Component/Webhook/Tests/Controller/WebhookControllerTest.php +++ b/src/Symfony/Component/Webhook/Tests/Controller/WebhookControllerTest.php @@ -32,7 +32,10 @@ public function testNoParserAvailable() $this->assertSame(404, $response->getStatusCode()); } - public function testParserRejectsPayload() + /** + * @dataProvider rejectedParseProvider + */ + public function testParserRejectsPayload($return) { $secret = '1234'; $request = new Request(); @@ -41,7 +44,7 @@ public function testParserRejectsPayload() ->expects($this->once()) ->method('parse') ->with($request, $secret) - ->willReturn(null); + ->willReturn($return); $parser ->expects($this->once()) ->method('createRejectedResponse') @@ -59,7 +62,13 @@ public function testParserRejectsPayload() $this->assertSame('Unable to parse the webhook payload.', $response->getContent()); } - public function testParserAcceptsPayload() + public static function rejectedParseProvider(): iterable + { + yield 'null' => [null]; + yield 'empty array' => [[]]; + } + + public function testParserAcceptsPayloadAndReturnsSingleEvent() { $secret = '1234'; $request = new Request(); @@ -97,4 +106,48 @@ public function dispatch(object $message, array $stamps = []): Envelope $this->assertSame('foo', $bus->message->getType()); $this->assertEquals($event, $bus->message->getEvent()); } + + public function testParserAcceptsPayloadAndReturnsMultipleEvents() + { + $secret = '1234'; + $request = new Request(); + $event1 = new RemoteEvent('name1', 'id1', ['payload1']); + $event2 = new RemoteEvent('name2', 'id2', ['payload2']); + $parser = $this->createMock(RequestParserInterface::class); + $parser + ->expects($this->once()) + ->method('parse') + ->with($request, $secret) + ->willReturn([$event1, $event2]); + $parser + ->expects($this->once()) + ->method('createSuccessfulResponse') + ->with($request) + ->willReturn(new Response('', 202)); + $bus = new class implements MessageBusInterface { + public array $messages = []; + + public function dispatch(object $message, array $stamps = []): Envelope + { + return new Envelope($this->messages[] = $message, $stamps); + } + }; + + $controller = new WebhookController( + ['foo' => ['parser' => $parser, 'secret' => $secret]], + $bus, + ); + + $response = $controller->handle('foo', $request); + + $this->assertSame(202, $response->getStatusCode()); + $this->assertSame('', $response->getContent()); + $this->assertCount(2, $bus->messages); + $this->assertInstanceOf(ConsumeRemoteEventMessage::class, $bus->messages[0]); + $this->assertSame('foo', $bus->messages[0]->getType()); + $this->assertEquals($event1, $bus->messages[0]->getEvent()); + $this->assertInstanceOf(ConsumeRemoteEventMessage::class, $bus->messages[1]); + $this->assertSame('foo', $bus->messages[1]->getType()); + $this->assertEquals($event2, $bus->messages[1]->getEvent()); + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy