From af6339bf93e8e90e513b61309a1f0add585e1cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 13 Oct 2018 00:59:07 +0200 Subject: [PATCH 01/13] Initial commit --- src/Symfony/Component/Mercure/CHANGELOG.md | 7 ++ src/Symfony/Component/Mercure/LICENSE | 19 ++++ src/Symfony/Component/Mercure/Publisher.php | 92 +++++++++++++++++++ src/Symfony/Component/Mercure/README.md | 17 ++++ .../Component/Mercure/StaticJwtProvider.php | 34 +++++++ .../Component/Mercure/Tests/PublisherTest.php | 44 +++++++++ .../Mercure/Tests/StaticJwtProviderTest.php | 28 ++++++ .../Component/Mercure/Tests/UpdateTest.php | 53 +++++++++++ src/Symfony/Component/Mercure/Update.php | 79 ++++++++++++++++ src/Symfony/Component/Mercure/composer.json | 39 ++++++++ .../Component/Mercure/phpunit.xml.dist | 30 ++++++ 11 files changed, 442 insertions(+) create mode 100644 src/Symfony/Component/Mercure/CHANGELOG.md create mode 100644 src/Symfony/Component/Mercure/LICENSE create mode 100644 src/Symfony/Component/Mercure/Publisher.php create mode 100644 src/Symfony/Component/Mercure/README.md create mode 100644 src/Symfony/Component/Mercure/StaticJwtProvider.php create mode 100644 src/Symfony/Component/Mercure/Tests/PublisherTest.php create mode 100644 src/Symfony/Component/Mercure/Tests/StaticJwtProviderTest.php create mode 100644 src/Symfony/Component/Mercure/Tests/UpdateTest.php create mode 100644 src/Symfony/Component/Mercure/Update.php create mode 100644 src/Symfony/Component/Mercure/composer.json create mode 100644 src/Symfony/Component/Mercure/phpunit.xml.dist diff --git a/src/Symfony/Component/Mercure/CHANGELOG.md b/src/Symfony/Component/Mercure/CHANGELOG.md new file mode 100644 index 0000000000000..88434b415edfb --- /dev/null +++ b/src/Symfony/Component/Mercure/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +4.2.0 +----- + + * Introduced the component as experimental diff --git a/src/Symfony/Component/Mercure/LICENSE b/src/Symfony/Component/Mercure/LICENSE new file mode 100644 index 0000000000000..ad399a798d6d2 --- /dev/null +++ b/src/Symfony/Component/Mercure/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Mercure/Publisher.php b/src/Symfony/Component/Mercure/Publisher.php new file mode 100644 index 0000000000000..aeaa31a4b1821 --- /dev/null +++ b/src/Symfony/Component/Mercure/Publisher.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Mercure; + +/** + * Publishes an update to the hub. + * + * @author Kévin Dunglas + */ +final class Publisher +{ + private $publishEndpoint; + private $jwtProvider; + private $httpClient; + + // TODO: choose a HTTP client library + public function __construct(string $publishEndpoint, callable $jwtProvider, callable $httpClient = null) + { + $this->publishEndpoint = $publishEndpoint; + $this->jwtProvider = $jwtProvider; + $this->httpClient = $httpClient ?? array($this, 'publish'); + } + + public function __invoke(Update $update) + { + $postData = array( + 'topic' => $update->getTopics(), + 'data' => $update->getData(), + 'target' => $update->getTargets(), + 'id' => $update->getId(), + 'type' => $update->getType(), + 'retry' => $update->getRetry(), + ); + + ($this->httpClient)($this->publishEndpoint, ($this->jwtProvider)(), $this->buildQuery($postData)); + } + + /** + * Similar to http_build_query but doesn't add the brackets in keys for array values and skip null values. + */ + private function buildQuery(array $data): string + { + $parts = array(); + foreach ($data as $key => $value) { + if (null === $value) { + continue; + } + + if (\is_array($value)) { + foreach ($value as $v) { + $parts[] = $this->encode($key, $v); + } + + continue; + } + + $parts[] = $this->encode($key, $value); + } + + return implode('&', $parts); + } + + private function encode($key, $value): string + { + // All Mercure's keys are safe, so don't need to be encoded, but it's not a generic solution + return sprintf('%s=%s', $key, urlencode($value)); + } + + private function publish(string $url, string $jwt, string $postData) + { + $result = @file_get_contents($this->publishEndpoint, false, stream_context_create(array('http' => array( + 'method' => 'POST', + 'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer $jwt", + 'content' => $postData, + )))); + + if (false === $result) { + throw new \RuntimeException('Unable to publish the update to the Mercure hub.'); + } + } +} diff --git a/src/Symfony/Component/Mercure/README.md b/src/Symfony/Component/Mercure/README.md new file mode 100644 index 0000000000000..fc61e5629a57b --- /dev/null +++ b/src/Symfony/Component/Mercure/README.md @@ -0,0 +1,17 @@ +Mercure Component +================= + +> Mercure is a protocol allowing to push data updates to web browsers and other + HTTP clients in a convenient, fast, reliable and battery-efficient way. + It is especially useful to publish real-time updates of resources served through + web APIs, to reactive web and mobile apps. + +The Mercure Component implements the "publisher" part of [the Mercure Protocol](https://mercure.rocks). + +Resources +--------- + + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Mercure/StaticJwtProvider.php b/src/Symfony/Component/Mercure/StaticJwtProvider.php new file mode 100644 index 0000000000000..389a48b97a487 --- /dev/null +++ b/src/Symfony/Component/Mercure/StaticJwtProvider.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Mercure; + +/** + * Provides a JWT passed as a configuration parameter. + * + * @author Kévin Dunglas + */ +final class StaticJwtProvider +{ + private $jwt; + + public function __construct(string $jwt) + { + $this->jwt = $jwt; + } + + public function __invoke(): string + { + return $this->jwt; + } +} diff --git a/src/Symfony/Component/Mercure/Tests/PublisherTest.php b/src/Symfony/Component/Mercure/Tests/PublisherTest.php new file mode 100644 index 0000000000000..d2412c14e439f --- /dev/null +++ b/src/Symfony/Component/Mercure/Tests/PublisherTest.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Mercure\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mercure\Publisher; +use Symfony\Component\Mercure\Update; + +/** + * @author Kévin Dunglas + */ +class PublisherTest extends TestCase +{ + const URL = 'https://demo.mercure.rocks/publish'; + const JWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU'; + + public function testPublish() + { + $jwtProvider = function () { + return self::JWT; + }; + + $httpClient = function (string $url, string $jwt, string $postData) { + $this->assertSame(self::URL, $url); + $this->assertSame(self::JWT, $jwt); + $this->assertSame('topic=https%3A%2F%2Fdemo.mercure.rocks%2Fdemo%2Fbooks%2F1.jsonld&data=Hi+from+Symfony%21', $postData); + }; + + // Set $httpClient to null to dispatch a real update through the demo hub + $publisher = new Publisher('https://demo.mercure.rocks/publish', $jwtProvider, $httpClient); + $publisher(new Update('https://demo.mercure.rocks/demo/books/1.jsonld', 'Hi from Symfony!')); + } +} diff --git a/src/Symfony/Component/Mercure/Tests/StaticJwtProviderTest.php b/src/Symfony/Component/Mercure/Tests/StaticJwtProviderTest.php new file mode 100644 index 0000000000000..26431c9aae93a --- /dev/null +++ b/src/Symfony/Component/Mercure/Tests/StaticJwtProviderTest.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Mercure\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mercure\StaticJwtProvider; + +class StaticJwtProviderTest extends TestCase +{ + const JWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJtZXJjdXJlLXRlc3QiLCJuYW1lIjoiS8OpdmluIER1bmdsYXMiLCJpYXQiOjE1MTYyMzkwMjJ9.n0KvJ31TCswaK7KuHiN22cLzpjC2UT2rhWqhIDprfmA'; + + public function testProvider() + { + $provider = new StaticJwtProvider(self::JWT); + $this->assertSame(self::JWT, $provider()); + } +} diff --git a/src/Symfony/Component/Mercure/Tests/UpdateTest.php b/src/Symfony/Component/Mercure/Tests/UpdateTest.php new file mode 100644 index 0000000000000..d709066738e58 --- /dev/null +++ b/src/Symfony/Component/Mercure/Tests/UpdateTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Mercure\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Mercure\Update; + +/** + * @author Kévin Dunglas + */ +class UpdateTest extends TestCase +{ + /** + * @dataProvider updateProvider + */ + public function testCreateUpdate($topics, $data, array $targets = array(), string $id = null, string $type = null, int $retry = null) + { + $update = new Update($topics, $data, $targets, $id, $type, $retry); + $this->assertSame((array) $topics, $update->getTopics()); + $this->assertSame($data, $update->getData()); + $this->assertSame($targets, $update->getTargets()); + $this->assertSame($id, $update->getId()); + $this->assertSame($type, $update->getType()); + $this->assertSame($retry, $update->getRetry()); + } + + public function updateProvider(): array + { + return array( + array('http://example.com/foo', 'payload', array('user-1', 'group-a'), 'id', 'type', 1936), + array(array('https://mercure.rocks', 'https://github.com/dunglas/mercure'), 'payload'), + ); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testInvalidTopic() + { + new Update(1, 'data'); + } +} diff --git a/src/Symfony/Component/Mercure/Update.php b/src/Symfony/Component/Mercure/Update.php new file mode 100644 index 0000000000000..2e37d9f4029d9 --- /dev/null +++ b/src/Symfony/Component/Mercure/Update.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Mercure; + +/** + * Represents an update to send to the hub. + * + * @see https://github.com/dunglas/mercure/blob/master/spec/mercure.md#hub + * @see https://github.com/dunglas/mercure/blob/master/hub/update.go + * + * @author Kévin Dunglas + */ +final class Update +{ + private $topics; + private $data; + private $targets; + private $id; + private $type; + private $retry; + + /** + * @param array|string $topics + */ + public function __construct($topics, string $data, array $targets = [], string $id = null, string $type = null, int $retry = null) + { + if (!\is_array($topics) && !\is_string($topics)) { + throw new \InvalidArgumentException('$topics must be an array of strings or a string'); + } + + $this->topics = (array) $topics; + $this->data = $data; + $this->targets = $targets; + $this->id = $id; + $this->type = $type; + $this->retry = $retry; + } + + public function getTopics(): array + { + return $this->topics; + } + + public function getData(): string + { + return $this->data; + } + + public function getTargets(): array + { + return $this->targets; + } + + public function getId(): ?string + { + return $this->id; + } + + public function getType(): ?string + { + return $this->type; + } + + public function getRetry(): ?int + { + return $this->retry; + } +} diff --git a/src/Symfony/Component/Mercure/composer.json b/src/Symfony/Component/Mercure/composer.json new file mode 100644 index 0000000000000..402c48ee88031 --- /dev/null +++ b/src/Symfony/Component/Mercure/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/mercure", + "type": "library", + "description": "Symfony Mercure Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Kévin Dunglas", + "email": "dunglas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "symfony/messenger": "~3.4|~4.0" + }, + "suggest": { + "symfony/messenger": "For using the Messenger integration." + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Mercure\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + } +} diff --git a/src/Symfony/Component/Mercure/phpunit.xml.dist b/src/Symfony/Component/Mercure/phpunit.xml.dist new file mode 100644 index 0000000000000..b4a9f0790e493 --- /dev/null +++ b/src/Symfony/Component/Mercure/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + + From 0a103520d59cfacd43446c04dbe058a14073dc04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 13 Oct 2018 01:04:25 +0200 Subject: [PATCH 02/13] Messenger support --- .../Mercure/Messenger/UpdateHandler.php | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/Symfony/Component/Mercure/Messenger/UpdateHandler.php diff --git a/src/Symfony/Component/Mercure/Messenger/UpdateHandler.php b/src/Symfony/Component/Mercure/Messenger/UpdateHandler.php new file mode 100644 index 0000000000000..eff9cd8790c03 --- /dev/null +++ b/src/Symfony/Component/Mercure/Messenger/UpdateHandler.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Symfony\Component\Mercure\Messenger; +use Symfony\Component\Mercure\Publisher; +use Symfony\Component\Mercure\Update; + +/** + * Publishes an update. + * + * @author Kévin Dunglas + */ +final class UpdateHandler +{ + private $publisher; + + public function __construct(Publisher $publisher) + { + $this->publisher = $publisher; + } + + public function __invoke(Update $update) + { + ($this->publisher)($update); + } +} From 899d06bae0ed509ad84e33a92ac3ba511c2077d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sat, 13 Oct 2018 10:00:23 +0200 Subject: [PATCH 03/13] Remove the messenger handler (use Publisher directly) --- .../Mercure/{ => Jwt}/StaticJwtProvider.php | 2 +- .../Mercure/Messenger/UpdateHandler.php | 36 ------------------- .../Tests/{ => Jwt}/StaticJwtProviderTest.php | 4 +-- src/Symfony/Component/Mercure/composer.json | 6 ---- 4 files changed, 3 insertions(+), 45 deletions(-) rename src/Symfony/Component/Mercure/{ => Jwt}/StaticJwtProvider.php (93%) delete mode 100644 src/Symfony/Component/Mercure/Messenger/UpdateHandler.php rename src/Symfony/Component/Mercure/Tests/{ => Jwt}/StaticJwtProviderTest.php (87%) diff --git a/src/Symfony/Component/Mercure/StaticJwtProvider.php b/src/Symfony/Component/Mercure/Jwt/StaticJwtProvider.php similarity index 93% rename from src/Symfony/Component/Mercure/StaticJwtProvider.php rename to src/Symfony/Component/Mercure/Jwt/StaticJwtProvider.php index 389a48b97a487..acaf049bcdddd 100644 --- a/src/Symfony/Component/Mercure/StaticJwtProvider.php +++ b/src/Symfony/Component/Mercure/Jwt/StaticJwtProvider.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace Symfony\Component\Mercure; +namespace Symfony\Component\Mercure\Jwt; /** * Provides a JWT passed as a configuration parameter. diff --git a/src/Symfony/Component/Mercure/Messenger/UpdateHandler.php b/src/Symfony/Component/Mercure/Messenger/UpdateHandler.php deleted file mode 100644 index eff9cd8790c03..0000000000000 --- a/src/Symfony/Component/Mercure/Messenger/UpdateHandler.php +++ /dev/null @@ -1,36 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Symfony\Component\Mercure\Messenger; -use Symfony\Component\Mercure\Publisher; -use Symfony\Component\Mercure\Update; - -/** - * Publishes an update. - * - * @author Kévin Dunglas - */ -final class UpdateHandler -{ - private $publisher; - - public function __construct(Publisher $publisher) - { - $this->publisher = $publisher; - } - - public function __invoke(Update $update) - { - ($this->publisher)($update); - } -} diff --git a/src/Symfony/Component/Mercure/Tests/StaticJwtProviderTest.php b/src/Symfony/Component/Mercure/Tests/Jwt/StaticJwtProviderTest.php similarity index 87% rename from src/Symfony/Component/Mercure/Tests/StaticJwtProviderTest.php rename to src/Symfony/Component/Mercure/Tests/Jwt/StaticJwtProviderTest.php index 26431c9aae93a..74901f198fde7 100644 --- a/src/Symfony/Component/Mercure/Tests/StaticJwtProviderTest.php +++ b/src/Symfony/Component/Mercure/Tests/Jwt/StaticJwtProviderTest.php @@ -11,10 +11,10 @@ declare(strict_types=1); -namespace Symfony\Component\Mercure\Tests; +namespace Symfony\Component\Mercure\Tests\Jwt; use PHPUnit\Framework\TestCase; -use Symfony\Component\Mercure\StaticJwtProvider; +use Symfony\Component\Mercure\Jwt\StaticJwtProvider; class StaticJwtProviderTest extends TestCase { diff --git a/src/Symfony/Component/Mercure/composer.json b/src/Symfony/Component/Mercure/composer.json index 402c48ee88031..548903549a745 100644 --- a/src/Symfony/Component/Mercure/composer.json +++ b/src/Symfony/Component/Mercure/composer.json @@ -18,12 +18,6 @@ "require": { "php": "^7.1.3" }, - "require-dev": { - "symfony/messenger": "~3.4|~4.0" - }, - "suggest": { - "symfony/messenger": "For using the Messenger integration." - }, "autoload": { "psr-4": { "Symfony\\Component\\Mercure\\": "" }, "exclude-from-classmap": [ From 0f6bfde34fc5aca83dc4359c1f7f83867da2cfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 14 Oct 2018 20:43:12 +0200 Subject: [PATCH 04/13] Handle errors. FWBundle config. --- .../DependencyInjection/Configuration.php | 36 +++++++++++++++ .../FrameworkExtension.php | 45 +++++++++++++++++++ .../Resources/config/schema/symfony-1.0.xsd | 16 +++++++ .../Fixtures/php/mercure.php | 13 ++++++ .../Fixtures/xml/mercure.xml | 13 ++++++ .../Fixtures/yml/mercure.yml | 6 +++ .../FrameworkExtensionTest.php | 9 ++++ src/Symfony/Component/Mercure/Publisher.php | 5 ++- 8 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mercure.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mercure.xml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mercure.yml diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 6775c7782674b..5ec0584166a7d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -15,6 +15,7 @@ use Doctrine\Common\Cache\Cache; use Symfony\Bundle\FullStack; use Symfony\Component\Asset\Package; +use Symfony\Component\Config\Definition\ArrayNode; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -22,6 +23,7 @@ use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; +use Symfony\Component\Mercure\Update; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; @@ -35,6 +37,7 @@ * * @author Jeremy Mikola * @author Grégoire Pineau + * @author Kévin Dunglas */ class Configuration implements ConfigurationInterface { @@ -106,6 +109,7 @@ public function getConfigTreeBuilder() $this->addWebLinkSection($rootNode); $this->addLockSection($rootNode); $this->addMessengerSection($rootNode); + $this->addMercureSection($rootNode); return $treeBuilder; } @@ -1132,4 +1136,36 @@ function ($a) { ->end() ; } + + private function addMercureSection(ArrayNodeDefinition $rootNode) + { + $rootNode + ->children() + ->arrayNode('mercure') + ->info('Mercure configuration') + ->{!class_exists(FullStack::class) && class_exists(Update::class) ? 'canBeDisabled' : 'canBeEnabled'}() + ->fixXmlConfig('hub') + ->children() + ->arrayNode('hubs') + ->useAttributeAsKey('name') + ->normalizeKeys(false) + ->arrayPrototype() + ->children() + ->scalarNode('url')->info('URL of the hub\'s publish endpoint')->example('https://demo.mercure.rocks/publish')->end() + ->scalarNode('jwt')->info('JSON Web Token to use to publish to this hub.')->end() + ->scalarNode('jwt_provider')->info('The ID of a service to call to generate the JSON Web Token.')->end() + ->scalarNode('bus')->info('Name of the Messenger bus where the handler for this hub must be registered. Default to the default bus if Messenger is enabled.')->end() + ->end() + ->validate() + ->ifTrue(function ($v) { return isset($v['jwt']) && isset($v['jwt_provider']); }) + ->thenInvalid('"jwt" and "jwt_provider" cannot be used together.') + ->end() + ->end() + ->end() + ->scalarNode('default_hub')->end() + ->end() + ->end() + ->end() + ; + } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 2e64bff2c03d7..a9688d3d992cf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -67,6 +67,9 @@ use Symfony\Component\Lock\Store\FlockStore; use Symfony\Component\Lock\Store\StoreFactory; use Symfony\Component\Lock\StoreInterface; +use Symfony\Component\Mercure\Jwt\StaticJwtProvider; +use Symfony\Component\Mercure\Publisher; +use Symfony\Component\Mercure\Update; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBusInterface; @@ -289,6 +292,10 @@ public function load(array $configs, ContainerBuilder $container) $this->registerLockConfiguration($config['lock'], $container, $loader); } + if ($this->isConfigEnabled($container, $config['mercure'])) { + $this->registerMercureConfiguration($config['mercure'], $container); + } + if ($this->isConfigEnabled($container, $config['web_link'])) { if (!class_exists(HttpHeaderSerializer::class)) { throw new LogicException('WebLink support cannot be enabled as the WebLink component is not installed.'); @@ -1668,6 +1675,44 @@ private function registerCacheConfiguration(array $config, ContainerBuilder $con } } + private function registerMercureConfiguration(array $config, ContainerBuilder $container) + { + if (!class_exists(Update::class)) { + throw new LogicException('Mercure support cannot be enabled as the Mercure component is not installed. Try running "composer require symfony/mercure".'); + } + + if (!$config['hubs']) { + return; + } + + $defaultHub = $config['default_hub'] ?? null; + foreach ($config['hubs'] as $name => $hub) { + if (isset($hub['jwt'])) { + $jwtProvider = sprintf('mercure.hub.%s.jwt_provider', $name); + $container->register($jwtProvider, StaticJwtProvider::class)->addArgument($hub['jwt']); + } else { + $jwtProvider = $hub['jwt_provider']; + } + + $hubId = sprintf('mercure.hub.%s.publisher', $name); + if (!$defaultHub) { + $defaultHub = $hubId; + } + + $publisherDefinition = $container->register($hubId, Publisher::class) + ->addArgument($hub['url']) + ->addArgument(new Reference($jwtProvider)); + + $bus = $hub['bus'] ?? null; + if ($this->messengerConfigEnabled && false !== $bus) { + $attributes = null === $bus ? array() : array('bus' => $hub['bus']); + $publisherDefinition->addTag('messenger.message_handler', $attributes); + } + } + + $container->setAlias(Publisher::class, $defaultHub); + } + /** * Returns the base path for the XSD files. * diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 71d376abb2bb6..74a8be7c7cdb0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -32,6 +32,7 @@ + @@ -417,4 +418,19 @@ + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mercure.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mercure.php new file mode 100644 index 0000000000000..a5abb640074cf --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mercure.php @@ -0,0 +1,13 @@ +loadFromExtension('framework', array( + 'mercure' => array( + 'hubs' => array( + array( + 'name' => 'default', + 'url' => 'https://demo.mercure.rocks/publish', + 'jwt' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU', + ), + ), + ), +)); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mercure.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mercure.xml new file mode 100644 index 0000000000000..0df4000e6427e --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mercure.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mercure.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mercure.yml new file mode 100644 index 0000000000000..041c2ff202d2f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mercure.yml @@ -0,0 +1,6 @@ +framework: + mercure: + hubs: + default: + url: https://demo.mercure.rocks/publish + jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 260f3819900e0..9184b9dbb7614 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1239,6 +1239,15 @@ public function testSessionCookieSecureAuto() $this->assertEquals($expected, array_keys($container->getDefinition('session_listener')->getArgument(0)->getValues())); } + public function testMercure() + { + $container = $this->createContainerFromFile('mercure'); + $this->assertTrue($container->hasDefinition('mercure.hub.default.jwt_provider')); + $this->assertTrue($container->hasDefinition('mercure.hub.default.publisher')); + $this->assertSame('https://demo.mercure.rocks/publish', $container->getDefinition('mercure.hub.default.publisher')->getArgument(0)); + $this->assertSame('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU', $container->getDefinition('mercure.hub.default.jwt_provider')->getArgument(0)); + } + protected function createContainer(array $data = array()) { return new ContainerBuilder(new ParameterBag(array_merge(array( diff --git a/src/Symfony/Component/Mercure/Publisher.php b/src/Symfony/Component/Mercure/Publisher.php index aeaa31a4b1821..342a8bcd6ae34 100644 --- a/src/Symfony/Component/Mercure/Publisher.php +++ b/src/Symfony/Component/Mercure/Publisher.php @@ -16,6 +16,8 @@ /** * Publishes an update to the hub. * + * Can be used as a Symfony Messenger handler too. + * * @author Kévin Dunglas */ final class Publisher @@ -24,7 +26,6 @@ final class Publisher private $jwtProvider; private $httpClient; - // TODO: choose a HTTP client library public function __construct(string $publishEndpoint, callable $jwtProvider, callable $httpClient = null) { $this->publishEndpoint = $publishEndpoint; @@ -86,7 +87,7 @@ private function publish(string $url, string $jwt, string $postData) )))); if (false === $result) { - throw new \RuntimeException('Unable to publish the update to the Mercure hub.'); + throw new \RuntimeException(sprintf('Unable to publish the update to the Mercure hub: %s', error_get_last())); } } } From c3682331bd2dfa7f411c24fe28d9b0be372ff0fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 15 Oct 2018 15:07:14 +0200 Subject: [PATCH 05/13] CS --- .../FrameworkBundle/DependencyInjection/Configuration.php | 1 - src/Symfony/Component/Mercure/Publisher.php | 4 ++-- src/Symfony/Component/Mercure/Update.php | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 5ec0584166a7d..3e02c8623aa71 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -15,7 +15,6 @@ use Doctrine\Common\Cache\Cache; use Symfony\Bundle\FullStack; use Symfony\Component\Asset\Package; -use Symfony\Component\Config\Definition\ArrayNode; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; diff --git a/src/Symfony/Component/Mercure/Publisher.php b/src/Symfony/Component/Mercure/Publisher.php index 342a8bcd6ae34..b2ce604f471db 100644 --- a/src/Symfony/Component/Mercure/Publisher.php +++ b/src/Symfony/Component/Mercure/Publisher.php @@ -81,8 +81,8 @@ private function encode($key, $value): string private function publish(string $url, string $jwt, string $postData) { $result = @file_get_contents($this->publishEndpoint, false, stream_context_create(array('http' => array( - 'method' => 'POST', - 'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer $jwt", + 'method' => 'POST', + 'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer $jwt", 'content' => $postData, )))); diff --git a/src/Symfony/Component/Mercure/Update.php b/src/Symfony/Component/Mercure/Update.php index 2e37d9f4029d9..d2bd2dc8fd7cc 100644 --- a/src/Symfony/Component/Mercure/Update.php +++ b/src/Symfony/Component/Mercure/Update.php @@ -33,7 +33,7 @@ final class Update /** * @param array|string $topics */ - public function __construct($topics, string $data, array $targets = [], string $id = null, string $type = null, int $retry = null) + public function __construct($topics, string $data, array $targets = array(), string $id = null, string $type = null, int $retry = null) { if (!\is_array($topics) && !\is_string($topics)) { throw new \InvalidArgumentException('$topics must be an array of strings or a string'); From cf5273efc4c1e6ed3f57b2a08d05b70b2e62c6d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Sun, 21 Oct 2018 22:43:16 +0200 Subject: [PATCH 06/13] Update for Mercure 0.2 --- src/Symfony/Component/Mercure/Publisher.php | 8 +++++--- .../Component/Mercure/Tests/PublisherTest.php | 16 ++++++++++------ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Mercure/Publisher.php b/src/Symfony/Component/Mercure/Publisher.php index b2ce604f471db..d796b01605491 100644 --- a/src/Symfony/Component/Mercure/Publisher.php +++ b/src/Symfony/Component/Mercure/Publisher.php @@ -33,7 +33,7 @@ public function __construct(string $publishEndpoint, callable $jwtProvider, call $this->httpClient = $httpClient ?? array($this, 'publish'); } - public function __invoke(Update $update) + public function __invoke(Update $update): string { $postData = array( 'topic' => $update->getTopics(), @@ -44,7 +44,7 @@ public function __invoke(Update $update) 'retry' => $update->getRetry(), ); - ($this->httpClient)($this->publishEndpoint, ($this->jwtProvider)(), $this->buildQuery($postData)); + return ($this->httpClient)($this->publishEndpoint, ($this->jwtProvider)(), $this->buildQuery($postData)); } /** @@ -78,7 +78,7 @@ private function encode($key, $value): string return sprintf('%s=%s', $key, urlencode($value)); } - private function publish(string $url, string $jwt, string $postData) + private function publish(string $url, string $jwt, string $postData): string { $result = @file_get_contents($this->publishEndpoint, false, stream_context_create(array('http' => array( 'method' => 'POST', @@ -89,5 +89,7 @@ private function publish(string $url, string $jwt, string $postData) if (false === $result) { throw new \RuntimeException(sprintf('Unable to publish the update to the Mercure hub: %s', error_get_last())); } + + return $result; } } diff --git a/src/Symfony/Component/Mercure/Tests/PublisherTest.php b/src/Symfony/Component/Mercure/Tests/PublisherTest.php index d2412c14e439f..7bdfad5d91968 100644 --- a/src/Symfony/Component/Mercure/Tests/PublisherTest.php +++ b/src/Symfony/Component/Mercure/Tests/PublisherTest.php @@ -22,8 +22,8 @@ */ class PublisherTest extends TestCase { - const URL = 'https://demo.mercure.rocks/publish'; - const JWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU'; + const URL = 'https://demo.mercure.rocks/hub'; + const JWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InN1YnNjcmliZSI6WyJmb28iLCJiYXIiXSwicHVibGlzaCI6WyJmb28iXX19.LRLvirgONK13JgacQ_VbcjySbVhkSmHy3IznH3tA9PM'; public function testPublish() { @@ -31,14 +31,18 @@ public function testPublish() return self::JWT; }; - $httpClient = function (string $url, string $jwt, string $postData) { + $httpClient = function (string $url, string $jwt, string $postData): string { $this->assertSame(self::URL, $url); $this->assertSame(self::JWT, $jwt); - $this->assertSame('topic=https%3A%2F%2Fdemo.mercure.rocks%2Fdemo%2Fbooks%2F1.jsonld&data=Hi+from+Symfony%21', $postData); + $this->assertSame('topic=https%3A%2F%2Fdemo.mercure.rocks%2Fdemo%2Fbooks%2F1.jsonld&data=Hi+from+Symfony%21&id=id', $postData); + + return 'id'; }; // Set $httpClient to null to dispatch a real update through the demo hub - $publisher = new Publisher('https://demo.mercure.rocks/publish', $jwtProvider, $httpClient); - $publisher(new Update('https://demo.mercure.rocks/demo/books/1.jsonld', 'Hi from Symfony!')); + $publisher = new Publisher(self::URL, $jwtProvider, $httpClient); + $id = $publisher(new Update('https://demo.mercure.rocks/demo/books/1.jsonld', 'Hi from Symfony!', array(), 'id')); + + $this->assertSame('id', $id); } } From ce7be488f38d81e63d62f7f0367941dd0425c289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 22 Oct 2018 22:58:10 +0200 Subject: [PATCH 07/13] Review --- .../Resources/config/schema/symfony-1.0.xsd | 5 +-- .../DependencyInjection/ConfigurationTest.php | 4 +++ src/Symfony/Component/Mercure/Publisher.php | 35 +++++++++++++++---- .../Component/Mercure/Tests/PublisherTest.php | 14 ++++++++ 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 74a8be7c7cdb0..03dc94d4e63fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -424,11 +424,12 @@ + - - + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index f431885a4d78b..49030ad4c55bb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -300,6 +300,10 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor 'default_bus' => null, 'buses' => array('messenger.bus.default' => array('default_middleware' => true, 'middleware' => array())), ), + 'mercure' => array( + 'enabled' => !class_exists(FullStack::class) && class_exists(Update::class), + 'hubs' => array(), + ), ); } } diff --git a/src/Symfony/Component/Mercure/Publisher.php b/src/Symfony/Component/Mercure/Publisher.php index d796b01605491..d7c410430cc56 100644 --- a/src/Symfony/Component/Mercure/Publisher.php +++ b/src/Symfony/Component/Mercure/Publisher.php @@ -22,13 +22,17 @@ */ final class Publisher { - private $publishEndpoint; + private $hubUrl; private $jwtProvider; private $httpClient; - public function __construct(string $publishEndpoint, callable $jwtProvider, callable $httpClient = null) + /** + * @param callable(): string $jwtProvider + * @param null|callable(string, callable(): string, string): string $httpClient + */ + public function __construct(string $hubUrl, callable $jwtProvider, callable $httpClient = null) { - $this->publishEndpoint = $publishEndpoint; + $this->hubUrl = $hubUrl; $this->jwtProvider = $jwtProvider; $this->httpClient = $httpClient ?? array($this, 'publish'); } @@ -44,7 +48,10 @@ public function __invoke(Update $update): string 'retry' => $update->getRetry(), ); - return ($this->httpClient)($this->publishEndpoint, ($this->jwtProvider)(), $this->buildQuery($postData)); + $jwt = ($this->jwtProvider)(); + $this->validateJwt($jwt); + + return ($this->httpClient)($this->hubUrl, $jwt, $this->buildQuery($postData)); } /** @@ -80,16 +87,32 @@ private function encode($key, $value): string private function publish(string $url, string $jwt, string $postData): string { - $result = @file_get_contents($this->publishEndpoint, false, stream_context_create(array('http' => array( + $result = @file_get_contents($this->hubUrl, false, stream_context_create(array('http' => array( 'method' => 'POST', 'header' => "Content-type: application/x-www-form-urlencoded\r\nAuthorization: Bearer $jwt", 'content' => $postData, )))); if (false === $result) { - throw new \RuntimeException(sprintf('Unable to publish the update to the Mercure hub: %s', error_get_last())); + throw new \RuntimeException(sprintf('Unable to publish the update to the Mercure hub: %s', error_get_last()['message'] ?? 'unknown error')); } return $result; } + + /** + * Regex ported from Windows Azure Active Directory IdentityModel Extensions for .Net. + * + * @throws \InvalidArgumentException + * + * @license MIT + * @copyright Copyright (c) Microsoft Corporation + * @see https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/6e7a53e241e4566998d3bf365f03acd0da699a31/src/Microsoft.IdentityModel.JsonWebTokens/JwtConstants.cs#L58 + */ + private function validateJwt(string $jwt): void + { + if (!preg_match('/^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]*$/', $jwt)) { + throw new \InvalidArgumentException('The provided JWT is not valid'); + } + } } diff --git a/src/Symfony/Component/Mercure/Tests/PublisherTest.php b/src/Symfony/Component/Mercure/Tests/PublisherTest.php index 7bdfad5d91968..1a99c86a2e491 100644 --- a/src/Symfony/Component/Mercure/Tests/PublisherTest.php +++ b/src/Symfony/Component/Mercure/Tests/PublisherTest.php @@ -45,4 +45,18 @@ public function testPublish() $this->assertSame('id', $id); } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage The provided JWT is not valid + */ + public function testInvalidJwt() + { + $jwtProvider = function () { + return "invalid\r\njwt"; + }; + + $publisher = new Publisher(self::URL, $jwtProvider); + $publisher(new Update('https://demo.mercure.rocks/demo/books/1.jsonld', 'Hi from Symfony!')); + } } From 661005bb1d0660384b2cb5ab13e33fd8c51f0241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 22 Oct 2018 23:00:52 +0200 Subject: [PATCH 08/13] Fix tests --- .../Tests/DependencyInjection/FrameworkExtensionTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 9184b9dbb7614..88ae2a6799137 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -36,6 +36,7 @@ use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpKernel\DependencyInjection\LoggerPass; +use Symfony\Component\Mercure\Update; use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage; use Symfony\Component\Messenger\Tests\Fixtures\SecondMessage; use Symfony\Component\Messenger\Transport\TransportFactory; @@ -1241,6 +1242,10 @@ public function testSessionCookieSecureAuto() public function testMercure() { + if (!class_exists(Update::class)) { + $this->markTestSkipped('The Mercure Component has been introduced in Symfony 4.2.'); + } + $container = $this->createContainerFromFile('mercure'); $this->assertTrue($container->hasDefinition('mercure.hub.default.jwt_provider')); $this->assertTrue($container->hasDefinition('mercure.hub.default.publisher')); From 06b812bf424d71ad837ee802bb4fbaa578cacfda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 23 Oct 2018 07:50:42 +0200 Subject: [PATCH 09/13] Fix CS --- src/Symfony/Component/Mercure/Publisher.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Component/Mercure/Publisher.php b/src/Symfony/Component/Mercure/Publisher.php index d7c410430cc56..f5b336ee0ac52 100644 --- a/src/Symfony/Component/Mercure/Publisher.php +++ b/src/Symfony/Component/Mercure/Publisher.php @@ -107,6 +107,7 @@ private function publish(string $url, string $jwt, string $postData): string * * @license MIT * @copyright Copyright (c) Microsoft Corporation + * * @see https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/6e7a53e241e4566998d3bf365f03acd0da699a31/src/Microsoft.IdentityModel.JsonWebTokens/JwtConstants.cs#L58 */ private function validateJwt(string $jwt): void From a381f46098e85ba4c72eca1dbf01ab5df7da3b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 23 Oct 2018 08:32:53 +0200 Subject: [PATCH 10/13] Fix remaining old URLs --- .../FrameworkBundle/DependencyInjection/Configuration.php | 2 +- .../Tests/DependencyInjection/Fixtures/xml/mercure.xml | 2 +- .../Tests/DependencyInjection/FrameworkExtensionTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 3e02c8623aa71..e06555ed06706 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -1150,7 +1150,7 @@ private function addMercureSection(ArrayNodeDefinition $rootNode) ->normalizeKeys(false) ->arrayPrototype() ->children() - ->scalarNode('url')->info('URL of the hub\'s publish endpoint')->example('https://demo.mercure.rocks/publish')->end() + ->scalarNode('url')->info('URL of the hub\'s publish endpoint')->example('https://demo.mercure.rocks/hub')->end() ->scalarNode('jwt')->info('JSON Web Token to use to publish to this hub.')->end() ->scalarNode('jwt_provider')->info('The ID of a service to call to generate the JSON Web Token.')->end() ->scalarNode('bus')->info('Name of the Messenger bus where the handler for this hub must be registered. Default to the default bus if Messenger is enabled.')->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mercure.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mercure.xml index 0df4000e6427e..f6f054d9ca47b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mercure.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/mercure.xml @@ -7,7 +7,7 @@ - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 88ae2a6799137..0d014f3b03e66 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1249,7 +1249,7 @@ public function testMercure() $container = $this->createContainerFromFile('mercure'); $this->assertTrue($container->hasDefinition('mercure.hub.default.jwt_provider')); $this->assertTrue($container->hasDefinition('mercure.hub.default.publisher')); - $this->assertSame('https://demo.mercure.rocks/publish', $container->getDefinition('mercure.hub.default.publisher')->getArgument(0)); + $this->assertSame('https://demo.mercure.rocks/hub', $container->getDefinition('mercure.hub.default.publisher')->getArgument(0)); $this->assertSame('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU', $container->getDefinition('mercure.hub.default.jwt_provider')->getArgument(0)); } From 421279da1a9bd03badadc52c12f0c48cb8a36d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 23 Oct 2018 08:49:21 +0200 Subject: [PATCH 11/13] Fix tests --- .../Tests/DependencyInjection/Fixtures/php/mercure.php | 2 +- .../Tests/DependencyInjection/Fixtures/yml/mercure.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mercure.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mercure.php index a5abb640074cf..db5a38c19bf8c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mercure.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/mercure.php @@ -5,7 +5,7 @@ 'hubs' => array( array( 'name' => 'default', - 'url' => 'https://demo.mercure.rocks/publish', + 'url' => 'https://demo.mercure.rocks/hub', 'jwt' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU', ), ), diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mercure.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mercure.yml index 041c2ff202d2f..170186d7409ec 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mercure.yml +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/mercure.yml @@ -2,5 +2,5 @@ framework: mercure: hubs: default: - url: https://demo.mercure.rocks/publish + url: https://demo.mercure.rocks/hub jwt: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU From 75b4a9b1dbc7c672337ab4182e1c252869614f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 23 Oct 2018 11:01:49 +0200 Subject: [PATCH 12/13] Mark the component as experimental --- src/Symfony/Component/Mercure/Jwt/StaticJwtProvider.php | 2 ++ src/Symfony/Component/Mercure/Publisher.php | 2 ++ src/Symfony/Component/Mercure/Update.php | 2 ++ 3 files changed, 6 insertions(+) diff --git a/src/Symfony/Component/Mercure/Jwt/StaticJwtProvider.php b/src/Symfony/Component/Mercure/Jwt/StaticJwtProvider.php index acaf049bcdddd..12672aa344760 100644 --- a/src/Symfony/Component/Mercure/Jwt/StaticJwtProvider.php +++ b/src/Symfony/Component/Mercure/Jwt/StaticJwtProvider.php @@ -17,6 +17,8 @@ * Provides a JWT passed as a configuration parameter. * * @author Kévin Dunglas + * + * @experimental */ final class StaticJwtProvider { diff --git a/src/Symfony/Component/Mercure/Publisher.php b/src/Symfony/Component/Mercure/Publisher.php index f5b336ee0ac52..2fb0b15ef6300 100644 --- a/src/Symfony/Component/Mercure/Publisher.php +++ b/src/Symfony/Component/Mercure/Publisher.php @@ -19,6 +19,8 @@ * Can be used as a Symfony Messenger handler too. * * @author Kévin Dunglas + * + * @experimental */ final class Publisher { diff --git a/src/Symfony/Component/Mercure/Update.php b/src/Symfony/Component/Mercure/Update.php index d2bd2dc8fd7cc..8e872653c9576 100644 --- a/src/Symfony/Component/Mercure/Update.php +++ b/src/Symfony/Component/Mercure/Update.php @@ -20,6 +20,8 @@ * @see https://github.com/dunglas/mercure/blob/master/hub/update.go * * @author Kévin Dunglas + * + * @experimental */ final class Update { From 0645bcd8495adebaf47ee669aafa1ca8bae3c493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Tue, 23 Oct 2018 21:46:59 +0200 Subject: [PATCH 13/13] Add some useful container's parameters --- .../DependencyInjection/FrameworkExtension.php | 6 ++++++ .../Tests/DependencyInjection/FrameworkExtensionTest.php | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index a9688d3d992cf..cad387572a947 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -1686,6 +1686,8 @@ private function registerMercureConfiguration(array $config, ContainerBuilder $c } $defaultHub = $config['default_hub'] ?? null; + $hubUrls = array(); + $defaultHubUrl = null; foreach ($config['hubs'] as $name => $hub) { if (isset($hub['jwt'])) { $jwtProvider = sprintf('mercure.hub.%s.jwt_provider', $name); @@ -1694,8 +1696,10 @@ private function registerMercureConfiguration(array $config, ContainerBuilder $c $jwtProvider = $hub['jwt_provider']; } + $hubUrls[$name] = $hub['url']; $hubId = sprintf('mercure.hub.%s.publisher', $name); if (!$defaultHub) { + $defaultHubUrl = $hub['url']; $defaultHub = $hubId; } @@ -1711,6 +1715,8 @@ private function registerMercureConfiguration(array $config, ContainerBuilder $c } $container->setAlias(Publisher::class, $defaultHub); + $container->setParameter('mercure.hubs', $hubUrls); + $container->setParameter('mercure.default_hub', $defaultHubUrl); } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 0d014f3b03e66..64cf739a766ac 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -1251,6 +1251,8 @@ public function testMercure() $this->assertTrue($container->hasDefinition('mercure.hub.default.publisher')); $this->assertSame('https://demo.mercure.rocks/hub', $container->getDefinition('mercure.hub.default.publisher')->getArgument(0)); $this->assertSame('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.HB0k08BaV8KlLZ3EafCRlTDGbkd9qdznCzJQ_l8ELTU', $container->getDefinition('mercure.hub.default.jwt_provider')->getArgument(0)); + $this->assertSame(array('default' => 'https://demo.mercure.rocks/hub'), $container->getParameter('mercure.hubs')); + $this->assertSame('https://demo.mercure.rocks/hub', $container->getParameter('mercure.default_hub')); } protected function createContainer(array $data = array())