From 349b7299c6d54c9b56757ba3b924336bf7163069 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 2 May 2022 12:31:26 +0200 Subject: [PATCH 1/4] add fast publishing method --- src/Hub.php | 47 ++++++++++++++++++++++++++--- src/HubInterface.php | 12 ++++++++ src/MockHub.php | 22 ++++++++++++++ tests/AuthorizationTest.php | 35 +++++++++++++++------ tests/HubRegistryTest.php | 30 +++++++++++++----- tests/Twig/MercureExtensionTest.php | 10 ++++-- 6 files changed, 132 insertions(+), 24 deletions(-) diff --git a/src/Hub.php b/src/Hub.php index 19935fe..dd74f31 100644 --- a/src/Hub.php +++ b/src/Hub.php @@ -18,6 +18,7 @@ use Symfony\Component\Mercure\Jwt\TokenProviderInterface; use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Saif Eddin Gmati @@ -82,6 +83,21 @@ public function getFactory(): ?TokenFactoryInterface * {@inheritDoc} */ public function publish(Update $update): string + { + $jwt = $this->getProvider()->getJwt(); + $this->validateJwt($jwt); + + try { + return $this->publishFast($update, $jwt)->getContent(); + } catch (ExceptionInterface $exception) { + throw new Exception\RuntimeException('Failed to send an update.', 0, $exception); + } + } + + /** + * {@inheritDoc} + */ + public function publishFast(Update $update, ?string $token = null): ResponseInterface { $postData = [ 'topic' => $update->getTopics(), @@ -92,14 +108,37 @@ public function publish(Update $update): string 'retry' => $update->getRetry(), ]; + if (!$token) { + $token = $this->getProvider()->getJwt(); + $this->validateJwt($token); + } + return $this->httpClient->request('POST', $this->getUrl(), [ + 'auth_bearer' => $token, + 'body' => Internal\QueryBuilder::build($postData), + ]); + } + + /** + * {@inheritDoc} + */ + public function publishBatch($updates, bool $fireAndForget = false): array + { + $jwt = $this->getProvider()->getJwt(); $this->validateJwt($jwt); try { - return $this->httpClient->request('POST', $this->getUrl(), [ - 'auth_bearer' => $jwt, - 'body' => Internal\QueryBuilder::build($postData), - ])->getContent(); + $requests = []; + foreach ($updates as $update) { + $requests[] = $this->publishFast($update, $jwt); + } + if ($fireAndForget) { + return []; + } else { + return array_map(function ($val) { + return $val->getContent(); + }, $requests); + } } catch (ExceptionInterface $exception) { throw new Exception\RuntimeException('Failed to send an update.', 0, $exception); } diff --git a/src/HubInterface.php b/src/HubInterface.php index 46d24e3..33be053 100644 --- a/src/HubInterface.php +++ b/src/HubInterface.php @@ -15,6 +15,7 @@ use Symfony\Component\Mercure\Jwt\TokenFactoryInterface; use Symfony\Component\Mercure\Jwt\TokenProviderInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Saif Eddin Gmati @@ -50,4 +51,15 @@ public function getFactory(): ?TokenFactoryInterface; * Publish an update to this Hub. */ public function publish(Update $update): string; + + /** + * Publish an update to this Hub. + * + */ + public function publishFast(Update $update, ?string $token = null): ResponseInterface; + /** + * Publish updates to this Hub. + * @param Iterable $updates + */ + public function publishBatch($updates, bool $fireAndForget = false): array; } diff --git a/src/MockHub.php b/src/MockHub.php index 093db32..be28f30 100644 --- a/src/MockHub.php +++ b/src/MockHub.php @@ -15,6 +15,7 @@ use Symfony\Component\Mercure\Jwt\TokenFactoryInterface; use Symfony\Component\Mercure\Jwt\TokenProviderInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; final class MockHub implements HubInterface { @@ -62,7 +63,28 @@ public function getFactory(): ?TokenFactoryInterface } public function publish(Update $update): string + { + return ($this->publisher)($update)->getContent(); + } + + public function publishFast(Update $update, ?string $token = null): ResponseInterface { return ($this->publisher)($update); } + + public function publishBatch($updates, bool $fireAndForget = false): array + { + $requests = []; + $token = null; + foreach ($updates as $update) { + $requests[] = $this->publishFast($update, $token); + } + if ($fireAndForget) { + return []; + } else { + return array_map(function ($val) { + return $val->getContent(); + }, $requests); + } + } } diff --git a/tests/AuthorizationTest.php b/tests/AuthorizationTest.php index ca174ce..b9ba6b1 100644 --- a/tests/AuthorizationTest.php +++ b/tests/AuthorizationTest.php @@ -15,6 +15,7 @@ use Lcobucci\JWT\Signer\Key\InMemory; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Mercure\Authorization; use Symfony\Component\Mercure\Exception\RuntimeException; @@ -24,6 +25,7 @@ use Symfony\Component\Mercure\Jwt\TokenFactoryInterface; use Symfony\Component\Mercure\MockHub; use Symfony\Component\Mercure\Update; +use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Kévin Dunglas @@ -39,7 +41,9 @@ public function testJwtLifetime(): void $registry = new HubRegistry(new MockHub( 'https://example.com/.well-known/mercure', new StaticTokenProvider('foo.bar.baz'), - function (Update $u): string { return 'dummy'; }, + function (Update $u): ResponseInterface { + return new MockResponse('dummy'); + }, new LcobucciFactory('secret', 'hmac.sha256', 3600) )); @@ -57,13 +61,14 @@ public function testSetCookie(): void $tokenFactory ->expects($this->once()) ->method('create') - ->with($this->equalTo(['foo']), $this->equalTo(['bar']), $this->arrayHasKey('x-foo')) - ; + ->with($this->equalTo(['foo']), $this->equalTo(['bar']), $this->arrayHasKey('x-foo')); $registry = new HubRegistry(new MockHub( 'https://example.com/.well-known/mercure', new StaticTokenProvider('foo.bar.baz'), - function (Update $u): string { return 'dummy'; }, + function (Update $u): ResponseInterface { + return new MockResponse('dummy'); + }, $tokenFactory )); @@ -81,8 +86,11 @@ public function testClearCookie(): void $registry = new HubRegistry(new MockHub( 'https://example.com/.well-known/mercure', new StaticTokenProvider('foo.bar.baz'), - function (Update $u): string { return 'dummy'; }, - new class() implements TokenFactoryInterface { + function (Update $u): ResponseInterface { + return new MockResponse('dummy'); + }, + new class() implements TokenFactoryInterface + { public function create(array $subscribe = [], array $publish = [], array $additionalClaims = []): string { return ''; @@ -111,7 +119,9 @@ public function testApplicableCookieDomains(?string $expected, string $hubUrl, s $registry = new HubRegistry(new MockHub( $hubUrl, new StaticTokenProvider('foo.bar.baz'), - function (Update $u): string { return 'dummy'; }, + function (Update $u): ResponseInterface { + return new MockResponse('dummy'); + }, new LcobucciFactory('secret', 'hmac.sha256', 3600) )); @@ -143,7 +153,9 @@ public function testNonApplicableCookieDomains(string $hubUrl, string $requestUr $registry = new HubRegistry(new MockHub( $hubUrl, new StaticTokenProvider('foo.bar.baz'), - function (Update $u): string { return 'dummy'; }, + function (Update $u): ResponseInterface { + return new MockResponse('dummy'); + }, new LcobucciFactory('secret', 'hmac.sha256', 3600) )); @@ -168,8 +180,11 @@ public function testSetMultipleCookies(): void $registry = new HubRegistry(new MockHub( 'https://example.com/.well-known/mercure', new StaticTokenProvider('foo.bar.baz'), - function (Update $u): string { return 'dummy'; }, - new class() implements TokenFactoryInterface { + function (Update $u): ResponseInterface { + return new MockResponse('dummy'); + }, + new class() implements TokenFactoryInterface + { public function create(array $subscribe = [], array $publish = [], array $additionalClaims = []): string { return ''; diff --git a/tests/HubRegistryTest.php b/tests/HubRegistryTest.php index 013b7c4..b13cdae 100644 --- a/tests/HubRegistryTest.php +++ b/tests/HubRegistryTest.php @@ -18,13 +18,19 @@ use Symfony\Component\Mercure\HubRegistry; use Symfony\Component\Mercure\Jwt\StaticTokenProvider; use Symfony\Component\Mercure\MockHub; +use Symfony\Component\HttpClient\Response\MockResponse; +use Symfony\Contracts\HttpClient\ResponseInterface; class HubRegistryTest extends TestCase { public function testGetHubByName(): void { - $fooHub = new MockHub('fooUrl', new StaticTokenProvider('fooToken'), static function (): string { return 'foo'; }); - $barHub = new MockHub('barUrl', new StaticTokenProvider('barToken'), static function (): string { return 'bar'; }); + $fooHub = new MockHub('fooUrl', new StaticTokenProvider('fooToken'), static function (): ResponseInterface { + return new MockResponse('foo'); + }); + $barHub = new MockHub('barUrl', new StaticTokenProvider('barToken'), static function (): ResponseInterface { + return new MockResponse('bar'); + }); $registry = new HubRegistry($fooHub, ['foo' => $fooHub, 'bar' => $barHub]); $this->assertSame($fooHub, $registry->getHub('foo')); @@ -32,8 +38,12 @@ public function testGetHubByName(): void public function testGetDefaultHub(): void { - $fooHub = new MockHub('fooUrl', new StaticTokenProvider('fooToken'), static function (): string { return 'foo'; }); - $barHub = new MockHub('barUrl', new StaticTokenProvider('barToken'), static function (): string { return 'bar'; }); + $fooHub = new MockHub('fooUrl', new StaticTokenProvider('fooToken'), static function (): ResponseInterface { + return new MockResponse('foo'); + }); + $barHub = new MockHub('barUrl', new StaticTokenProvider('barToken'), static function (): ResponseInterface { + return new MockResponse('bar'); + }); $registry = new HubRegistry($fooHub, ['foo' => $fooHub, 'bar' => $barHub]); $this->assertSame($fooHub, $registry->getHub()); @@ -41,7 +51,9 @@ public function testGetDefaultHub(): void public function testGetMissingHubThrows(): void { - $fooHub = new MockHub('fooUrl', new StaticTokenProvider('fooToken'), static function (): string { return 'foo'; }); + $fooHub = new MockHub('fooUrl', new StaticTokenProvider('fooToken'), static function (): ResponseInterface { + return new MockResponse('foo'); + }); $registry = new HubRegistry($fooHub, ['foo' => $fooHub]); $this->expectException(InvalidArgumentException::class); @@ -50,8 +62,12 @@ public function testGetMissingHubThrows(): void public function testGetAllHubs(): void { - $fooHub = new MockHub('fooUrl', new StaticTokenProvider('fooToken'), static function (): string { return 'foo'; }); - $barHub = new MockHub('barUrl', new StaticTokenProvider('barToken'), static function (): string { return 'bar'; }); + $fooHub = new MockHub('fooUrl', new StaticTokenProvider('fooToken'), static function (): ResponseInterface { + return new MockResponse('foo'); + }); + $barHub = new MockHub('barUrl', new StaticTokenProvider('barToken'), static function (): ResponseInterface { + return new MockResponse('bar'); + }); $registry = new HubRegistry($fooHub, ['foo' => $fooHub, 'bar' => $barHub]); $this->assertSame(['foo' => $fooHub, 'bar' => $barHub], $registry->all()); diff --git a/tests/Twig/MercureExtensionTest.php b/tests/Twig/MercureExtensionTest.php index ffe8c86..90fe72d 100644 --- a/tests/Twig/MercureExtensionTest.php +++ b/tests/Twig/MercureExtensionTest.php @@ -22,7 +22,9 @@ use Symfony\Component\Mercure\Jwt\StaticTokenProvider; use Symfony\Component\Mercure\Jwt\TokenFactoryInterface; use Symfony\Component\Mercure\MockHub; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\Mercure\Twig\MercureExtension; +use Symfony\Contracts\HttpClient\ResponseInterface; use Symfony\Component\Mercure\Update; /** @@ -35,7 +37,9 @@ public function testMercure(): void $registry = new HubRegistry(new MockHub( 'https://example.com/.well-known/mercure', new StaticTokenProvider('foo.bar.baz'), - function (Update $u): string { return 'dummy'; }, + function (Update $u): ResponseInterface { + return new MockResponse('dummy'); + }, $this->createMock(TokenFactoryInterface::class) )); @@ -56,8 +60,8 @@ public function testMercureLastEventId(): void $registry = new HubRegistry(new MockHub( 'https://example.com/.well-known/mercure', new StaticTokenProvider('foo.bar.baz'), - function (Update $u): string { - return 'dummy'; + function (Update $u): ResponseInterface { + return new MockResponse('dummy'); }, $this->createMock(TokenFactoryInterface::class) )); From e5dd9c96fff6cf220a706c3749563beb02541174 Mon Sep 17 00:00:00 2001 From: kaftan Date: Mon, 2 May 2022 13:41:28 +0200 Subject: [PATCH 2/4] fix TraceableHub, fix type annotation --- src/Debug/TraceableHub.php | 32 ++++++++++++++++++++++++++++++++ src/MockHub.php | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Debug/TraceableHub.php b/src/Debug/TraceableHub.php index bfb0037..73d4a04 100644 --- a/src/Debug/TraceableHub.php +++ b/src/Debug/TraceableHub.php @@ -19,6 +19,7 @@ use Symfony\Component\Mercure\Update; use Symfony\Component\Stopwatch\Stopwatch; use Symfony\Contracts\Service\ResetInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; /** * Traces updates for profiler. @@ -74,6 +75,37 @@ public function publish(Update $update): string return $content; } + public function publishFast(Update $update): ResponseInterface + { + $this->stopwatch->start(__CLASS__); + $content = $this->hub->publishFast($update); + + $e = $this->stopwatch->stop(__CLASS__); + $this->messages[] = [ + 'object' => $update, + 'duration' => $e->getDuration(), + 'memory' => $e->getMemory(), + ]; + + return $content; + } + + public function publishBatch($updates, bool $fireAndForget = false): array + { + $this->stopwatch->start(__CLASS__); + $content = $this->hub->publishBatch($updates); + + $e = $this->stopwatch->stop(__CLASS__); + $this->messages[] = [ + 'object' => $updates, + 'duration' => $e->getDuration(), + 'memory' => $e->getMemory(), + ]; + + return $content; + + } + public function reset(): void { $this->messages = []; diff --git a/src/MockHub.php b/src/MockHub.php index be28f30..a0ed557 100644 --- a/src/MockHub.php +++ b/src/MockHub.php @@ -26,7 +26,7 @@ final class MockHub implements HubInterface private $publicUrl; /** - * @param (callable(Update): string) $publisher + * @param (callable(Update): ResponseInterface) $publisher */ public function __construct( string $url, From 2b0e3c4e9f13803b46aacf13a2cd0a7f5d629a30 Mon Sep 17 00:00:00 2001 From: kaftan Date: Mon, 2 May 2022 13:47:48 +0200 Subject: [PATCH 3/4] fixes 2 --- src/Debug/TraceableHub.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Debug/TraceableHub.php b/src/Debug/TraceableHub.php index 73d4a04..e46ac2e 100644 --- a/src/Debug/TraceableHub.php +++ b/src/Debug/TraceableHub.php @@ -75,10 +75,10 @@ public function publish(Update $update): string return $content; } - public function publishFast(Update $update): ResponseInterface + public function publishFast(Update $update, ?string $token = null): ResponseInterface { $this->stopwatch->start(__CLASS__); - $content = $this->hub->publishFast($update); + $content = $this->hub->publishFast($update, $token); $e = $this->stopwatch->stop(__CLASS__); $this->messages[] = [ From f2b94021298fd2054ab1da7bed30227f4d920e69 Mon Sep 17 00:00:00 2001 From: kaftan Date: Tue, 3 May 2022 09:34:20 +0200 Subject: [PATCH 4/4] apply fabbot codestyle fixes --- src/Debug/TraceableHub.php | 3 +-- src/Hub.php | 12 ++++++------ src/HubInterface.php | 5 +++-- src/MockHub.php | 8 ++++---- tests/AuthorizationTest.php | 6 ++---- tests/HubRegistryTest.php | 2 +- tests/Twig/MercureExtensionTest.php | 4 ++-- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Debug/TraceableHub.php b/src/Debug/TraceableHub.php index e46ac2e..7193cbb 100644 --- a/src/Debug/TraceableHub.php +++ b/src/Debug/TraceableHub.php @@ -18,8 +18,8 @@ use Symfony\Component\Mercure\Jwt\TokenProviderInterface; use Symfony\Component\Mercure\Update; use Symfony\Component\Stopwatch\Stopwatch; -use Symfony\Contracts\Service\ResetInterface; use Symfony\Contracts\HttpClient\ResponseInterface; +use Symfony\Contracts\Service\ResetInterface; /** * Traces updates for profiler. @@ -103,7 +103,6 @@ public function publishBatch($updates, bool $fireAndForget = false): array ]; return $content; - } public function reset(): void diff --git a/src/Hub.php b/src/Hub.php index dd74f31..056e2f8 100644 --- a/src/Hub.php +++ b/src/Hub.php @@ -112,6 +112,7 @@ public function publishFast(Update $update, ?string $token = null): ResponseInte $token = $this->getProvider()->getJwt(); $this->validateJwt($token); } + return $this->httpClient->request('POST', $this->getUrl(), [ 'auth_bearer' => $token, 'body' => Internal\QueryBuilder::build($postData), @@ -123,7 +124,6 @@ public function publishFast(Update $update, ?string $token = null): ResponseInte */ public function publishBatch($updates, bool $fireAndForget = false): array { - $jwt = $this->getProvider()->getJwt(); $this->validateJwt($jwt); @@ -134,11 +134,11 @@ public function publishBatch($updates, bool $fireAndForget = false): array } if ($fireAndForget) { return []; - } else { - return array_map(function ($val) { - return $val->getContent(); - }, $requests); } + + return array_map(function ($val) { + return $val->getContent(); + }, $requests); } catch (ExceptionInterface $exception) { throw new Exception\RuntimeException('Failed to send an update.', 0, $exception); } @@ -157,7 +157,7 @@ public function publishBatch($updates, bool $fireAndForget = false): array private function validateJwt(string $jwt): void { if (!preg_match('/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/', $jwt)) { - throw new Exception\InvalidArgumentException('The provided JWT is not valid'); + throw new Exception\InvalidArgumentException('The provided JWT is not valid.'); } } } diff --git a/src/HubInterface.php b/src/HubInterface.php index 33be053..1423129 100644 --- a/src/HubInterface.php +++ b/src/HubInterface.php @@ -54,12 +54,13 @@ public function publish(Update $update): string; /** * Publish an update to this Hub. - * */ public function publishFast(Update $update, ?string $token = null): ResponseInterface; + /** * Publish updates to this Hub. - * @param Iterable $updates + * + * @param iterable $updates */ public function publishBatch($updates, bool $fireAndForget = false): array; } diff --git a/src/MockHub.php b/src/MockHub.php index a0ed557..c2857ab 100644 --- a/src/MockHub.php +++ b/src/MockHub.php @@ -81,10 +81,10 @@ public function publishBatch($updates, bool $fireAndForget = false): array } if ($fireAndForget) { return []; - } else { - return array_map(function ($val) { - return $val->getContent(); - }, $requests); } + + return array_map(function ($val) { + return $val->getContent(); + }, $requests); } } diff --git a/tests/AuthorizationTest.php b/tests/AuthorizationTest.php index b9ba6b1..6416ee8 100644 --- a/tests/AuthorizationTest.php +++ b/tests/AuthorizationTest.php @@ -89,8 +89,7 @@ public function testClearCookie(): void function (Update $u): ResponseInterface { return new MockResponse('dummy'); }, - new class() implements TokenFactoryInterface - { + new class() implements TokenFactoryInterface { public function create(array $subscribe = [], array $publish = [], array $additionalClaims = []): string { return ''; @@ -183,8 +182,7 @@ public function testSetMultipleCookies(): void function (Update $u): ResponseInterface { return new MockResponse('dummy'); }, - new class() implements TokenFactoryInterface - { + new class() implements TokenFactoryInterface { public function create(array $subscribe = [], array $publish = [], array $additionalClaims = []): string { return ''; diff --git a/tests/HubRegistryTest.php b/tests/HubRegistryTest.php index b13cdae..151c2b5 100644 --- a/tests/HubRegistryTest.php +++ b/tests/HubRegistryTest.php @@ -14,11 +14,11 @@ namespace Symfony\Component\Mercure\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\Mercure\Exception\InvalidArgumentException; use Symfony\Component\Mercure\HubRegistry; use Symfony\Component\Mercure\Jwt\StaticTokenProvider; use Symfony\Component\Mercure\MockHub; -use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Contracts\HttpClient\ResponseInterface; class HubRegistryTest extends TestCase diff --git a/tests/Twig/MercureExtensionTest.php b/tests/Twig/MercureExtensionTest.php index 90fe72d..ded1b8f 100644 --- a/tests/Twig/MercureExtensionTest.php +++ b/tests/Twig/MercureExtensionTest.php @@ -14,6 +14,7 @@ namespace Symfony\Component\Mercure\Tests\Twig; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -22,10 +23,9 @@ use Symfony\Component\Mercure\Jwt\StaticTokenProvider; use Symfony\Component\Mercure\Jwt\TokenFactoryInterface; use Symfony\Component\Mercure\MockHub; -use Symfony\Component\HttpClient\Response\MockResponse; use Symfony\Component\Mercure\Twig\MercureExtension; -use Symfony\Contracts\HttpClient\ResponseInterface; use Symfony\Component\Mercure\Update; +use Symfony\Contracts\HttpClient\ResponseInterface; /** * @author Kévin Dunglas