diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..ff202ff
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,16 @@
+* text=auto
+
+/.github export-ignore
+/bin export-ignore
+/config export-ignore
+/tests export-ignore
+/var export-ignore
+/.env export-ignore
+/.env.test export-ignore
+/.gitattributes export-ignore
+/.gitignore export-ignore
+/composer.lock export-ignore
+/phpcs.xml.dist export-ignore
+/phpstan.neon export-ignore
+/phpunit.xml.dist export-ignore
+/symfony.lock export-ignore
diff --git a/README.md b/README.md
index 261820c..ba7deee 100644
--- a/README.md
+++ b/README.md
@@ -17,11 +17,43 @@ See what features are covered and what aren't (yet) [here](#feature-coverage).
PHP 8.1 is required to run the hub.
+### As a standalone Mercure hub
+
+```bash
+composer create-project freddie/mercure-x freddie && cd freddie
+bin/freddie
+```
+
+This will start a Freddie instance on `127.0.0.1:8080`, with anonymous subscriptions enabled.
+
+You can publish updates to the hub by generating a valid JWT signed with the `!ChangeMe!` key with `HMAC SHA256` algorithm.
+
+To change these values, see [Security](#security).
+
+### As a bundle of your existing Symfony application
+
+```bash
+composer req freddie/mercure-x
+```
+
+You can then start the hub by doing:
+
```bash
-composer create-project freddie/mercure-x:"~0.2" freddie
-cd freddie
+bin/console freddie:serve
```
+You can override relevant env vars in your `.env.local`
+and services in your `config/services.yaml` as usual.
+
+Then, you can inject `Freddie\Hub\HubInterface` in your services so that you can call `$hub->publish($update)`,
+or listening to dispatched updates in a CLI context 👍
+
+Keep in mind this only works when using the Redis transport.
+
+⚠️ **Freddie** uses its own routing/authentication system (because of async / event loop).
+
+The controllers it exposes cannot be imported in your `routes.yaml`, and get out of your `security.yaml` scope.
+
## Usage
```bash
@@ -35,6 +67,8 @@ To change this address, use the `X_LISTEN` environment variable:
X_LISTEN="0.0.0.0:8000" ./bin/freddie
```
+### Security
+
The default JWT key is `!ChangeMe!` with a `HS256` signature.
You can set different values by changing the environment variables (in `.env.local` or at the OS level):
@@ -42,10 +76,18 @@ You can set different values by changing the environment variables (in `.env.loc
Please refer to the [authorization](https://mercure.rocks/spec#authorization) section of the Mercure specification to authenticate as a publisher and/or a subscriber.
-### Redis transport
+### PHP Transport (default)
+
+By default, the hub will run as a simple event-dispatcher, in a single PHP process.
+
+It can fit common needs for a basic usage, but using this transport prevents scalability,
+as opening another process won't share the same event emitter.
-By default, the hub will run as a simple event-dispatcher, in a single PHP process.
-It can fit common needs for a basic usage, but is not scalable (opening another process won't share the same event emitter).
+It's still prefectly usable as soon as :
+- You don't expect more than a few hundreds updates per second
+- Your application is served from a single server.
+
+### Redis transport
On the other hand, you can launch the hub on **multiple ports** and/or **multiple servers** with a Redis transport
(as soon as they share the same Redis instance), and optionally use a load-balancer to distribute the traffic.
diff --git a/bin/freddie b/bin/freddie
index 469ad39..8fe1727 100755
--- a/bin/freddie
+++ b/bin/freddie
@@ -15,7 +15,7 @@ require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return static function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
$application = new Application($kernel);
- $application->setDefaultCommand('serve');
+ $application->setDefaultCommand('freddie:serve');
return $application;
};
diff --git a/composer.json b/composer.json
index bdbbadb..0c003ec 100644
--- a/composer.json
+++ b/composer.json
@@ -14,17 +14,17 @@
"ext-ctype": "*",
"ext-iconv": "*",
"bentools/querystring": "^1.1",
- "clue/framework-x": "dev-main",
+ "clue/framework-x": "dev-main#74fafca31a1d3d4c6faa3bf9276a9d018cb587fa",
"clue/redis-react": "^2.5",
"doctrine/annotations": "^1.0",
"lcobucci/jwt": "^4.1",
"nyholm/dsn": "^2.0",
"phpdocumentor/reflection-docblock": "^5.3",
- "react/async": "dev-main",
+ "react/async": "dev-main#ff11a7aa9eea7104af8f05bafbc85422dac4b8ab",
"rize/uri-template": "^0.3.4",
"symfony/console": "6.0.*",
"symfony/dotenv": "6.0.*",
- "symfony/flex": "^1.17",
+ "symfony/flex": "^1.17|^2",
"symfony/framework-bundle": "6.0.*",
"symfony/options-resolver": "6.0.*",
"symfony/property-access": "6.0.*",
@@ -64,11 +64,6 @@
}
},
"bin": ["bin/freddie"],
- "replace": {
- "symfony/polyfill-ctype": "*",
- "symfony/polyfill-iconv": "*",
- "symfony/polyfill-php72": "*"
- },
"scripts": {
"post-install-cmd": [
"bin/freddie cache:clear"
diff --git a/composer.lock b/composer.lock
index d365ff0..49e0706 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "414602a649e069d55914f87b1714a23e",
+ "content-hash": "93ebe5fcb2a96088bc850e34bc31af6a",
"packages": [
{
"name": "bentools/querystring",
@@ -318,32 +318,28 @@
},
{
"name": "doctrine/lexer",
- "version": "1.2.1",
+ "version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/lexer.git",
- "reference": "e864bbf5904cb8f5bb334f99209b48018522f042"
+ "reference": "9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042",
- "reference": "e864bbf5904cb8f5bb334f99209b48018522f042",
+ "url": "https://api.github.com/repos/doctrine/lexer/zipball/9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c",
+ "reference": "9c50f840f257bbb941e6f4a0e94ccf5db5c3f76c",
"shasum": ""
},
"require": {
- "php": "^7.2 || ^8.0"
+ "php": "^7.1 || ^8.0"
},
"require-dev": {
- "doctrine/coding-standard": "^6.0",
- "phpstan/phpstan": "^0.11.8",
- "phpunit/phpunit": "^8.2"
+ "doctrine/coding-standard": "^9.0",
+ "phpstan/phpstan": "1.3",
+ "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
+ "vimeo/psalm": "^4.11"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.2.x-dev"
- }
- },
"autoload": {
"psr-4": {
"Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
@@ -378,7 +374,7 @@
],
"support": {
"issues": "https://github.com/doctrine/lexer/issues",
- "source": "https://github.com/doctrine/lexer/tree/1.2.1"
+ "source": "https://github.com/doctrine/lexer/tree/1.2.2"
},
"funding": [
{
@@ -394,7 +390,7 @@
"type": "tidelift"
}
],
- "time": "2020-05-25T17:44:05+00:00"
+ "time": "2022-01-12T08:27:12+00:00"
},
{
"name": "evenement/evenement",
@@ -800,16 +796,16 @@
},
{
"name": "phpdocumentor/type-resolver",
- "version": "1.5.1",
+ "version": "1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae"
+ "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/a12f7e301eb7258bb68acd89d4aefa05c2906cae",
- "reference": "a12f7e301eb7258bb68acd89d4aefa05c2906cae",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706",
+ "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706",
"shasum": ""
},
"require": {
@@ -844,9 +840,9 @@
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
- "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.5.1"
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0"
},
- "time": "2021-10-02T14:08:47+00:00"
+ "time": "2022-01-04T19:58:01+00:00"
},
{
"name": "psr/cache",
@@ -1109,12 +1105,12 @@
"source": {
"type": "git",
"url": "https://github.com/reactphp/async.git",
- "reference": "80aa19fabcd2ca7c4a37c7079ced48b40bbb1c79"
+ "reference": "ff11a7aa9eea7104af8f05bafbc85422dac4b8ab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/reactphp/async/zipball/80aa19fabcd2ca7c4a37c7079ced48b40bbb1c79",
- "reference": "80aa19fabcd2ca7c4a37c7079ced48b40bbb1c79",
+ "url": "https://api.github.com/repos/reactphp/async/zipball/ff11a7aa9eea7104af8f05bafbc85422dac4b8ab",
+ "reference": "ff11a7aa9eea7104af8f05bafbc85422dac4b8ab",
"shasum": ""
},
"require": {
@@ -1180,7 +1176,7 @@
"type": "github"
}
],
- "time": "2021-11-22T14:32:42+00:00"
+ "time": "2022-01-05T06:37:17+00:00"
},
{
"name": "react/cache",
@@ -1731,16 +1727,16 @@
},
{
"name": "react/socket",
- "version": "v1.10.0",
+ "version": "v1.11.0",
"source": {
"type": "git",
"url": "https://github.com/reactphp/socket.git",
- "reference": "d132fde589ea97f4165f2d94b5296499eac125ec"
+ "reference": "f474156aaab4f09041144fa8b57c7d70aed32a1c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/reactphp/socket/zipball/d132fde589ea97f4165f2d94b5296499eac125ec",
- "reference": "d132fde589ea97f4165f2d94b5296499eac125ec",
+ "url": "https://api.github.com/repos/reactphp/socket/zipball/f474156aaab4f09041144fa8b57c7d70aed32a1c",
+ "reference": "f474156aaab4f09041144fa8b57c7d70aed32a1c",
"shasum": ""
},
"require": {
@@ -1749,11 +1745,11 @@
"react/dns": "^1.8",
"react/event-loop": "^1.2",
"react/promise": "^2.6.0 || ^1.2.1",
- "react/promise-timer": "^1.4.0",
+ "react/promise-timer": "^1.8",
"react/stream": "^1.2"
},
"require-dev": {
- "clue/block-react": "^1.2",
+ "clue/block-react": "^1.5",
"phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35",
"react/promise-stream": "^1.2"
},
@@ -1799,7 +1795,7 @@
],
"support": {
"issues": "https://github.com/reactphp/socket/issues",
- "source": "https://github.com/reactphp/socket/tree/v1.10.0"
+ "source": "https://github.com/reactphp/socket/tree/v1.11.0"
},
"funding": [
{
@@ -1811,7 +1807,7 @@
"type": "github"
}
],
- "time": "2021-11-29T10:08:24+00:00"
+ "time": "2022-01-14T10:14:32+00:00"
},
{
"name": "react/stream",
@@ -2016,16 +2012,16 @@
},
{
"name": "symfony/cache",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
- "reference": "67213eb02dfc41aa9acb01b8ba260d133c523b79"
+ "reference": "41bdcb2d067c68f338b0cfd46a86abd8309b4153"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/cache/zipball/67213eb02dfc41aa9acb01b8ba260d133c523b79",
- "reference": "67213eb02dfc41aa9acb01b8ba260d133c523b79",
+ "url": "https://api.github.com/repos/symfony/cache/zipball/41bdcb2d067c68f338b0cfd46a86abd8309b4153",
+ "reference": "41bdcb2d067c68f338b0cfd46a86abd8309b4153",
"shasum": ""
},
"require": {
@@ -2089,7 +2085,7 @@
"psr6"
],
"support": {
- "source": "https://github.com/symfony/cache/tree/v6.0.1"
+ "source": "https://github.com/symfony/cache/tree/v6.0.2"
},
"funding": [
{
@@ -2105,7 +2101,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-29T13:00:11+00:00"
},
{
"name": "symfony/cache-contracts",
@@ -2188,16 +2184,16 @@
},
{
"name": "symfony/config",
- "version": "v6.0.0",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "df4871981fd37f953c117b55feac03462be5a2d6"
+ "reference": "990e6d603da7b9556645e5689c7b082f564790e7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/df4871981fd37f953c117b55feac03462be5a2d6",
- "reference": "df4871981fd37f953c117b55feac03462be5a2d6",
+ "url": "https://api.github.com/repos/symfony/config/zipball/990e6d603da7b9556645e5689c7b082f564790e7",
+ "reference": "990e6d603da7b9556645e5689c7b082f564790e7",
"shasum": ""
},
"require": {
@@ -2246,7 +2242,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/config/tree/v6.0.0"
+ "source": "https://github.com/symfony/config/tree/v6.0.2"
},
"funding": [
{
@@ -2262,20 +2258,20 @@
"type": "tidelift"
}
],
- "time": "2021-11-23T19:05:29+00:00"
+ "time": "2021-12-28T14:01:53+00:00"
},
{
"name": "symfony/console",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "fafd9802d386bf1c267e0249ddb7ceb14dcfdad4"
+ "reference": "dd434fa8d69325e5d210f63070014d889511fcb3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/fafd9802d386bf1c267e0249ddb7ceb14dcfdad4",
- "reference": "fafd9802d386bf1c267e0249ddb7ceb14dcfdad4",
+ "url": "https://api.github.com/repos/symfony/console/zipball/dd434fa8d69325e5d210f63070014d889511fcb3",
+ "reference": "dd434fa8d69325e5d210f63070014d889511fcb3",
"shasum": ""
},
"require": {
@@ -2341,7 +2337,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.0.1"
+ "source": "https://github.com/symfony/console/tree/v6.0.2"
},
"funding": [
{
@@ -2357,20 +2353,20 @@
"type": "tidelift"
}
],
- "time": "2021-12-09T12:47:37+00:00"
+ "time": "2021-12-27T21:05:08+00:00"
},
{
"name": "symfony/dependency-injection",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "401f794b342585772c1d22288cafbce597485093"
+ "reference": "a9346ef44ea8a7b60f1f1edc8d802ffb91baa6a8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/401f794b342585772c1d22288cafbce597485093",
- "reference": "401f794b342585772c1d22288cafbce597485093",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a9346ef44ea8a7b60f1f1edc8d802ffb91baa6a8",
+ "reference": "a9346ef44ea8a7b60f1f1edc8d802ffb91baa6a8",
"shasum": ""
},
"require": {
@@ -2429,7 +2425,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/dependency-injection/tree/v6.0.1"
+ "source": "https://github.com/symfony/dependency-injection/tree/v6.0.2"
},
"funding": [
{
@@ -2445,7 +2441,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-29T10:14:09+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -2516,16 +2512,16 @@
},
{
"name": "symfony/dotenv",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/dotenv.git",
- "reference": "b8491dc698e76a2b6255c641346f1958bd65f5cf"
+ "reference": "5c43c5515e549a8c2c426be36d40f47daf196968"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dotenv/zipball/b8491dc698e76a2b6255c641346f1958bd65f5cf",
- "reference": "b8491dc698e76a2b6255c641346f1958bd65f5cf",
+ "url": "https://api.github.com/repos/symfony/dotenv/zipball/5c43c5515e549a8c2c426be36d40f47daf196968",
+ "reference": "5c43c5515e549a8c2c426be36d40f47daf196968",
"shasum": ""
},
"require": {
@@ -2566,7 +2562,7 @@
"environment"
],
"support": {
- "source": "https://github.com/symfony/dotenv/tree/v6.0.1"
+ "source": "https://github.com/symfony/dotenv/tree/v6.0.2"
},
"funding": [
{
@@ -2582,20 +2578,20 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-16T22:05:41+00:00"
},
{
"name": "symfony/error-handler",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "944193d25c564c8c80411a5d02eb2be823d57d5c"
+ "reference": "3e677f0c14a529bc542025c96cfa9638227b4cc6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/944193d25c564c8c80411a5d02eb2be823d57d5c",
- "reference": "944193d25c564c8c80411a5d02eb2be823d57d5c",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/3e677f0c14a529bc542025c96cfa9638227b4cc6",
+ "reference": "3e677f0c14a529bc542025c96cfa9638227b4cc6",
"shasum": ""
},
"require": {
@@ -2637,7 +2633,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v6.0.1"
+ "source": "https://github.com/symfony/error-handler/tree/v6.0.2"
},
"funding": [
{
@@ -2653,20 +2649,20 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-21T13:16:58+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "4f06d19a5f78087061f9de6df3269c139c3d289d"
+ "reference": "7093f25359e2750bfe86842c80c4e4a6a852d05c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4f06d19a5f78087061f9de6df3269c139c3d289d",
- "reference": "4f06d19a5f78087061f9de6df3269c139c3d289d",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7093f25359e2750bfe86842c80c4e4a6a852d05c",
+ "reference": "7093f25359e2750bfe86842c80c4e4a6a852d05c",
"shasum": ""
},
"require": {
@@ -2720,7 +2716,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/event-dispatcher/tree/v6.0.1"
+ "source": "https://github.com/symfony/event-dispatcher/tree/v6.0.2"
},
"funding": [
{
@@ -2736,7 +2732,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-21T10:43:13+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -2882,16 +2878,16 @@
},
{
"name": "symfony/finder",
- "version": "v6.0.0",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "07debda41a4d32d33e59e6ab302af1701e15f173"
+ "reference": "03d2833e677d48317cac852f9c0287fb048c3c5c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/07debda41a4d32d33e59e6ab302af1701e15f173",
- "reference": "07debda41a4d32d33e59e6ab302af1701e15f173",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/03d2833e677d48317cac852f9c0287fb048c3c5c",
+ "reference": "03d2833e677d48317cac852f9c0287fb048c3c5c",
"shasum": ""
},
"require": {
@@ -2923,7 +2919,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v6.0.0"
+ "source": "https://github.com/symfony/finder/tree/v6.0.2"
},
"funding": [
{
@@ -2939,32 +2935,32 @@
"type": "tidelift"
}
],
- "time": "2021-11-28T15:34:37+00:00"
+ "time": "2021-12-20T16:21:45+00:00"
},
{
"name": "symfony/flex",
- "version": "v1.17.6",
+ "version": "v2.0.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/flex.git",
- "reference": "7a79135e1dc66b30042b4d968ecba0908f9374bc"
+ "reference": "3dbfa5c4e3308fd9def9a2006a20fa0c272a30a2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/flex/zipball/7a79135e1dc66b30042b4d968ecba0908f9374bc",
- "reference": "7a79135e1dc66b30042b4d968ecba0908f9374bc",
+ "url": "https://api.github.com/repos/symfony/flex/zipball/3dbfa5c4e3308fd9def9a2006a20fa0c272a30a2",
+ "reference": "3dbfa5c4e3308fd9def9a2006a20fa0c272a30a2",
"shasum": ""
},
"require": {
- "composer-plugin-api": "^1.0|^2.0",
- "php": ">=7.1"
+ "composer-plugin-api": "^2.1",
+ "php": ">=8.0"
},
"require-dev": {
- "composer/composer": "^1.0.2|^2.0",
- "symfony/dotenv": "^4.4|^5.0|^6.0",
- "symfony/filesystem": "^4.4|^5.0|^6.0",
- "symfony/phpunit-bridge": "^4.4.12|^5.0|^6.0",
- "symfony/process": "^4.4|^5.0|^6.0"
+ "composer/composer": "^2.1",
+ "symfony/dotenv": "^5.4|^6.0",
+ "symfony/filesystem": "^5.4|^6.0",
+ "symfony/phpunit-bridge": "^5.4|^6.0",
+ "symfony/process": "^5.4|^6.0"
},
"type": "composer-plugin",
"extra": {
@@ -2988,7 +2984,7 @@
"description": "Composer plugin for Symfony",
"support": {
"issues": "https://github.com/symfony/flex/issues",
- "source": "https://github.com/symfony/flex/tree/v1.17.6"
+ "source": "https://github.com/symfony/flex/tree/v2.0.1"
},
"funding": [
{
@@ -3004,20 +3000,20 @@
"type": "tidelift"
}
],
- "time": "2021-11-29T15:39:37+00:00"
+ "time": "2021-11-29T15:40:20+00:00"
},
{
"name": "symfony/framework-bundle",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/framework-bundle.git",
- "reference": "15131cd085d3f5568634015f6885b85cf8a2a2f7"
+ "reference": "d0598be96b193c4c2e5abab56af512a8e10b3540"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/15131cd085d3f5568634015f6885b85cf8a2a2f7",
- "reference": "15131cd085d3f5568634015f6885b85cf8a2a2f7",
+ "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/d0598be96b193c4c2e5abab56af512a8e10b3540",
+ "reference": "d0598be96b193c4c2e5abab56af512a8e10b3540",
"shasum": ""
},
"require": {
@@ -3138,7 +3134,7 @@
"description": "Provides a tight integration between Symfony components and the Symfony full-stack framework",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/framework-bundle/tree/v6.0.1"
+ "source": "https://github.com/symfony/framework-bundle/tree/v6.0.2"
},
"funding": [
{
@@ -3154,20 +3150,20 @@
"type": "tidelift"
}
],
- "time": "2021-12-09T12:47:37+00:00"
+ "time": "2021-12-22T00:01:56+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "4c55dff16ba400dc81c56b6234e5942f9b9c7bcc"
+ "reference": "ac1cd9b84bdea9a3a06cd2127e910afc8b276798"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4c55dff16ba400dc81c56b6234e5942f9b9c7bcc",
- "reference": "4c55dff16ba400dc81c56b6234e5942f9b9c7bcc",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ac1cd9b84bdea9a3a06cd2127e910afc8b276798",
+ "reference": "ac1cd9b84bdea9a3a06cd2127e910afc8b276798",
"shasum": ""
},
"require": {
@@ -3210,7 +3206,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v6.0.1"
+ "source": "https://github.com/symfony/http-foundation/tree/v6.0.2"
},
"funding": [
{
@@ -3226,20 +3222,20 @@
"type": "tidelift"
}
],
- "time": "2021-12-09T12:47:37+00:00"
+ "time": "2021-12-28T17:22:37+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "cd7ed5337e67e1be91526184006fe7c1603283cb"
+ "reference": "00743bc336421a9be4f3e04464969ba8ef3517ad"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cd7ed5337e67e1be91526184006fe7c1603283cb",
- "reference": "cd7ed5337e67e1be91526184006fe7c1603283cb",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/00743bc336421a9be4f3e04464969ba8ef3517ad",
+ "reference": "00743bc336421a9be4f3e04464969ba8ef3517ad",
"shasum": ""
},
"require": {
@@ -3319,7 +3315,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v6.0.1"
+ "source": "https://github.com/symfony/http-kernel/tree/v6.0.2"
},
"funding": [
{
@@ -3335,7 +3331,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-09T13:42:47+00:00"
+ "time": "2021-12-29T14:07:16+00:00"
},
{
"name": "symfony/options-resolver",
@@ -3404,18 +3400,100 @@
],
"time": "2021-11-23T19:05:29+00:00"
},
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.24.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "30885182c981ab175d4d034db0f6f469898070ab"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
+ "reference": "30885182c981ab175d4d034db0f6f469898070ab",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.23-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.24.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2021-10-20T20:35:02+00:00"
+ },
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.23.1",
+ "version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "16880ba9c5ebe3642d1995ab866db29270b36535"
+ "reference": "81b86b50cf841a64252b439e738e97f4a34e2783"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/16880ba9c5ebe3642d1995ab866db29270b36535",
- "reference": "16880ba9c5ebe3642d1995ab866db29270b36535",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/81b86b50cf841a64252b439e738e97f4a34e2783",
+ "reference": "81b86b50cf841a64252b439e738e97f4a34e2783",
"shasum": ""
},
"require": {
@@ -3467,7 +3545,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.23.1"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.24.0"
},
"funding": [
{
@@ -3483,11 +3561,11 @@
"type": "tidelift"
}
],
- "time": "2021-05-27T12:26:48+00:00"
+ "time": "2021-11-23T21:10:46+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.23.0",
+ "version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
@@ -3551,7 +3629,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.23.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.24.0"
},
"funding": [
{
@@ -3571,21 +3649,24 @@
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.23.1",
+ "version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6"
+ "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6",
- "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
+ "reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
+ "provide": {
+ "ext-mbstring": "*"
+ },
"suggest": {
"ext-mbstring": "For best performance"
},
@@ -3631,7 +3712,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.24.0"
},
"funding": [
{
@@ -3647,20 +3728,20 @@
"type": "tidelift"
}
],
- "time": "2021-05-27T12:26:48+00:00"
+ "time": "2021-11-30T18:21:41+00:00"
},
{
"name": "symfony/polyfill-php81",
- "version": "v1.23.0",
+ "version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
- "reference": "e66119f3de95efc359483f810c4c3e6436279436"
+ "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/e66119f3de95efc359483f810c4c3e6436279436",
- "reference": "e66119f3de95efc359483f810c4c3e6436279436",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f",
+ "reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f",
"shasum": ""
},
"require": {
@@ -3710,7 +3791,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php81/tree/v1.23.0"
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.24.0"
},
"funding": [
{
@@ -3726,25 +3807,28 @@
"type": "tidelift"
}
],
- "time": "2021-05-21T13:25:03+00:00"
+ "time": "2021-09-13T13:58:11+00:00"
},
{
"name": "symfony/polyfill-uuid",
- "version": "v1.23.0",
+ "version": "v1.24.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-uuid.git",
- "reference": "9165effa2eb8a31bb3fa608df9d529920d21ddd9"
+ "reference": "7529922412d23ac44413d0f308861d50cf68d3ee"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/9165effa2eb8a31bb3fa608df9d529920d21ddd9",
- "reference": "9165effa2eb8a31bb3fa608df9d529920d21ddd9",
+ "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/7529922412d23ac44413d0f308861d50cf68d3ee",
+ "reference": "7529922412d23ac44413d0f308861d50cf68d3ee",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
+ "provide": {
+ "ext-uuid": "*"
+ },
"suggest": {
"ext-uuid": "For best performance"
},
@@ -3789,7 +3873,7 @@
"uuid"
],
"support": {
- "source": "https://github.com/symfony/polyfill-uuid/tree/v1.23.0"
+ "source": "https://github.com/symfony/polyfill-uuid/tree/v1.24.0"
},
"funding": [
{
@@ -3805,20 +3889,20 @@
"type": "tidelift"
}
],
- "time": "2021-02-19T12:13:01+00:00"
+ "time": "2021-10-20T20:35:02+00:00"
},
{
"name": "symfony/property-access",
- "version": "v6.0.0",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-access.git",
- "reference": "e0b66975319b4648e0cbf267878b07d8e2d11e2e"
+ "reference": "3f237ffd38a54592181ac62f63c6cabbca00051f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/property-access/zipball/e0b66975319b4648e0cbf267878b07d8e2d11e2e",
- "reference": "e0b66975319b4648e0cbf267878b07d8e2d11e2e",
+ "url": "https://api.github.com/repos/symfony/property-access/zipball/3f237ffd38a54592181ac62f63c6cabbca00051f",
+ "reference": "3f237ffd38a54592181ac62f63c6cabbca00051f",
"shasum": ""
},
"require": {
@@ -3868,7 +3952,7 @@
"reflection"
],
"support": {
- "source": "https://github.com/symfony/property-access/tree/v6.0.0"
+ "source": "https://github.com/symfony/property-access/tree/v6.0.2"
},
"funding": [
{
@@ -3884,20 +3968,20 @@
"type": "tidelift"
}
],
- "time": "2021-11-28T15:34:37+00:00"
+ "time": "2021-12-11T16:36:28+00:00"
},
{
"name": "symfony/property-info",
- "version": "v6.0.0",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/property-info.git",
- "reference": "56e98f48ee2dc89688d1870e66d834627a17db6d"
+ "reference": "fc23cfd8fe8faa0712a8909f956cf2e46c72f6cf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/property-info/zipball/56e98f48ee2dc89688d1870e66d834627a17db6d",
- "reference": "56e98f48ee2dc89688d1870e66d834627a17db6d",
+ "url": "https://api.github.com/repos/symfony/property-info/zipball/fc23cfd8fe8faa0712a8909f956cf2e46c72f6cf",
+ "reference": "fc23cfd8fe8faa0712a8909f956cf2e46c72f6cf",
"shasum": ""
},
"require": {
@@ -3957,7 +4041,7 @@
"validator"
],
"support": {
- "source": "https://github.com/symfony/property-info/tree/v6.0.0"
+ "source": "https://github.com/symfony/property-info/tree/v6.0.2"
},
"funding": [
{
@@ -3973,7 +4057,7 @@
"type": "tidelift"
}
],
- "time": "2021-11-04T18:05:01+00:00"
+ "time": "2021-12-27T21:05:08+00:00"
},
{
"name": "symfony/routing",
@@ -4141,16 +4225,16 @@
},
{
"name": "symfony/serializer",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/serializer.git",
- "reference": "60637437ca5bfa519e4085e9ea28ead456f9d85e"
+ "reference": "2282e7512a3264ef964cefce0a4a8037cd1044e5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/serializer/zipball/60637437ca5bfa519e4085e9ea28ead456f9d85e",
- "reference": "60637437ca5bfa519e4085e9ea28ead456f9d85e",
+ "url": "https://api.github.com/repos/symfony/serializer/zipball/2282e7512a3264ef964cefce0a4a8037cd1044e5",
+ "reference": "2282e7512a3264ef964cefce0a4a8037cd1044e5",
"shasum": ""
},
"require": {
@@ -4164,6 +4248,7 @@
"symfony/dependency-injection": "<5.4",
"symfony/property-access": "<5.4",
"symfony/property-info": "<5.4",
+ "symfony/uid": "<5.4",
"symfony/yaml": "<5.4"
},
"require-dev": {
@@ -4221,7 +4306,7 @@
"description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/serializer/tree/v6.0.1"
+ "source": "https://github.com/symfony/serializer/tree/v6.0.2"
},
"funding": [
{
@@ -4237,7 +4322,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-01T16:45:37+00:00"
+ "time": "2021-12-25T20:10:03+00:00"
},
{
"name": "symfony/service-contracts",
@@ -4323,16 +4408,16 @@
},
{
"name": "symfony/string",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32"
+ "reference": "bae261d0c3ac38a1f802b4dfed42094296100631"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/0cfed595758ec6e0a25591bdc8ca733c1896af32",
- "reference": "0cfed595758ec6e0a25591bdc8ca733c1896af32",
+ "url": "https://api.github.com/repos/symfony/string/zipball/bae261d0c3ac38a1f802b4dfed42094296100631",
+ "reference": "bae261d0c3ac38a1f802b4dfed42094296100631",
"shasum": ""
},
"require": {
@@ -4388,7 +4473,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v6.0.1"
+ "source": "https://github.com/symfony/string/tree/v6.0.2"
},
"funding": [
{
@@ -4404,20 +4489,20 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-16T22:13:01+00:00"
},
{
"name": "symfony/uid",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
- "reference": "6ef7c361b84f8ae666843279d08b2b8ce8006033"
+ "reference": "6750d730b5d1c002b366ec40ac811317d142d1f3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/uid/zipball/6ef7c361b84f8ae666843279d08b2b8ce8006033",
- "reference": "6ef7c361b84f8ae666843279d08b2b8ce8006033",
+ "url": "https://api.github.com/repos/symfony/uid/zipball/6750d730b5d1c002b366ec40ac811317d142d1f3",
+ "reference": "6750d730b5d1c002b366ec40ac811317d142d1f3",
"shasum": ""
},
"require": {
@@ -4458,10 +4543,11 @@
"homepage": "https://symfony.com",
"keywords": [
"UID",
+ "ulid",
"uuid"
],
"support": {
- "source": "https://github.com/symfony/uid/tree/v6.0.1"
+ "source": "https://github.com/symfony/uid/tree/v6.0.2"
},
"funding": [
{
@@ -4477,20 +4563,20 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-16T22:13:01+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "9ca4948ec35bb15175e5475ba83dfdb13042a86c"
+ "reference": "41e46f64084a205459a862751158ce2190bd5cb5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9ca4948ec35bb15175e5475ba83dfdb13042a86c",
- "reference": "9ca4948ec35bb15175e5475ba83dfdb13042a86c",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/41e46f64084a205459a862751158ce2190bd5cb5",
+ "reference": "41e46f64084a205459a862751158ce2190bd5cb5",
"shasum": ""
},
"require": {
@@ -4549,7 +4635,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v6.0.1"
+ "source": "https://github.com/symfony/var-dumper/tree/v6.0.2"
},
"funding": [
{
@@ -4565,7 +4651,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-29T10:14:09+00:00"
},
{
"name": "symfony/var-exporter",
@@ -4641,16 +4727,16 @@
},
{
"name": "symfony/yaml",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "d34390fe7e8c0fe7e4192c67c27ecf58bc7d3ed7"
+ "reference": "ed602f38b8636a2ea21af760d2578f3d2f92fc60"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/d34390fe7e8c0fe7e4192c67c27ecf58bc7d3ed7",
- "reference": "d34390fe7e8c0fe7e4192c67c27ecf58bc7d3ed7",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/ed602f38b8636a2ea21af760d2578f3d2f92fc60",
+ "reference": "ed602f38b8636a2ea21af760d2578f3d2f92fc60",
"shasum": ""
},
"require": {
@@ -4695,7 +4781,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.0.1"
+ "source": "https://github.com/symfony/yaml/tree/v6.0.2"
},
"funding": [
{
@@ -4711,7 +4797,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-16T22:13:01+00:00"
},
{
"name": "webmozart/assert",
@@ -4779,12 +4865,12 @@
"source": {
"type": "git",
"url": "https://github.com/clue/reactphp-eventsource.git",
- "reference": "8ca8624895f1576e5c3bcdd9c210841e6c45e400"
+ "reference": "7fc5855a52740d7e24b781bb8be45480fcd1d7aa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/clue/reactphp-eventsource/zipball/8ca8624895f1576e5c3bcdd9c210841e6c45e400",
- "reference": "8ca8624895f1576e5c3bcdd9c210841e6c45e400",
+ "url": "https://api.github.com/repos/clue/reactphp-eventsource/zipball/7fc5855a52740d7e24b781bb8be45480fcd1d7aa",
+ "reference": "7fc5855a52740d7e24b781bb8be45480fcd1d7aa",
"shasum": ""
},
"require": {
@@ -4837,7 +4923,7 @@
"type": "github"
}
],
- "time": "2021-12-20T08:12:47+00:00"
+ "time": "2021-12-22T12:01:58+00:00"
},
{
"name": "doctrine/instantiator",
@@ -4963,16 +5049,16 @@
},
{
"name": "filp/whoops",
- "version": "2.14.4",
+ "version": "2.14.5",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
- "reference": "f056f1fe935d9ed86e698905a957334029899895"
+ "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/filp/whoops/zipball/f056f1fe935d9ed86e698905a957334029899895",
- "reference": "f056f1fe935d9ed86e698905a957334029899895",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc",
+ "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc",
"shasum": ""
},
"require": {
@@ -5022,7 +5108,7 @@
],
"support": {
"issues": "https://github.com/filp/whoops/issues",
- "source": "https://github.com/filp/whoops/tree/2.14.4"
+ "source": "https://github.com/filp/whoops/tree/2.14.5"
},
"funding": [
{
@@ -5030,7 +5116,7 @@
"type": "github"
}
],
- "time": "2021-10-03T12:00:00+00:00"
+ "time": "2022-01-07T12:00:00+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -5148,31 +5234,31 @@
},
{
"name": "nunomaduro/collision",
- "version": "dev-develop",
+ "version": "v6.0.0",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
- "reference": "5f7d8c370e1602ac22032c2688b04af4c28e221d"
+ "reference": "5338ecc3909ef3ed150f6408f14e44cdb62bfdd0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/collision/zipball/5f7d8c370e1602ac22032c2688b04af4c28e221d",
- "reference": "5f7d8c370e1602ac22032c2688b04af4c28e221d",
+ "url": "https://api.github.com/repos/nunomaduro/collision/zipball/5338ecc3909ef3ed150f6408f14e44cdb62bfdd0",
+ "reference": "5338ecc3909ef3ed150f6408f14e44cdb62bfdd0",
"shasum": ""
},
"require": {
"facade/ignition-contracts": "^1.0.2",
- "filp/whoops": "^2.14.3",
+ "filp/whoops": "^2.14.5",
"php": "^8.0.0",
- "symfony/console": "^6.0.0"
+ "symfony/console": "^6.0.2"
},
"require-dev": {
- "laravel/framework": "8.x-dev",
- "nunomaduro/larastan": "^0.7.12",
+ "brianium/paratest": "^6.4.1",
+ "laravel/framework": "^9.0",
+ "nunomaduro/larastan": "^1.0.2",
"nunomaduro/mock-final-classes": "^1.1.0",
"orchestra/testbench": "^7.0.0",
- "phpstan/phpstan": "^0.12.94",
- "phpunit/phpunit": "^9.5.8"
+ "phpunit/phpunit": "^9.5.11"
},
"type": "library",
"extra": {
@@ -5219,7 +5305,7 @@
},
"funding": [
{
- "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L",
+ "url": "https://www.paypal.com/paypalme/enunomaduro",
"type": "custom"
},
{
@@ -5231,7 +5317,7 @@
"type": "patreon"
}
],
- "time": "2021-09-20T15:19:57+00:00"
+ "time": "2022-01-10T17:17:19+00:00"
},
{
"name": "pestphp/pest",
@@ -5598,16 +5684,16 @@
},
{
"name": "phpstan/phpstan",
- "version": "1.2.0",
+ "version": "1.4.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee"
+ "reference": "72b04d97b5e6e60a081f17c416fef35bd521120b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/cbe085f9fdead5b6d62e4c022ca52dc9427a10ee",
- "reference": "cbe085f9fdead5b6d62e4c022ca52dc9427a10ee",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/72b04d97b5e6e60a081f17c416fef35bd521120b",
+ "reference": "72b04d97b5e6e60a081f17c416fef35bd521120b",
"shasum": ""
},
"require": {
@@ -5623,7 +5709,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.2-dev"
+ "dev-master": "1.4-dev"
}
},
"autoload": {
@@ -5638,7 +5724,7 @@
"description": "PHPStan - PHP Static Analysis Tool",
"support": {
"issues": "https://github.com/phpstan/phpstan/issues",
- "source": "https://github.com/phpstan/phpstan/tree/1.2.0"
+ "source": "https://github.com/phpstan/phpstan/tree/1.4.0"
},
"funding": [
{
@@ -5658,7 +5744,7 @@
"type": "tidelift"
}
],
- "time": "2021-11-18T14:09:01+00:00"
+ "time": "2022-01-14T15:58:47+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -5980,16 +6066,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.5.10",
+ "version": "9.5.11",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a"
+ "reference": "2406855036db1102126125537adb1406f7242fdd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
- "reference": "c814a05837f2edb0d1471d6e3f4ab3501ca3899a",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2406855036db1102126125537adb1406f7242fdd",
+ "reference": "2406855036db1102126125537adb1406f7242fdd",
"shasum": ""
},
"require": {
@@ -6067,11 +6153,11 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.10"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.11"
},
"funding": [
{
- "url": "https://phpunit.de/donate.html",
+ "url": "https://phpunit.de/sponsors.html",
"type": "custom"
},
{
@@ -6079,7 +6165,7 @@
"type": "github"
}
],
- "time": "2021-09-25T07:38:51+00:00"
+ "time": "2021-12-25T07:07:57+00:00"
},
{
"name": "react/child-process",
@@ -7182,16 +7268,16 @@
},
{
"name": "symfony/http-client",
- "version": "v6.0.1",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
- "reference": "99e42b54cedf061d898aa796a0b3758598021607"
+ "reference": "7f1cbd44590cb0acc6208c1711a52733e9a91663"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/99e42b54cedf061d898aa796a0b3758598021607",
- "reference": "99e42b54cedf061d898aa796a0b3758598021607",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/7f1cbd44590cb0acc6208c1711a52733e9a91663",
+ "reference": "7f1cbd44590cb0acc6208c1711a52733e9a91663",
"shasum": ""
},
"require": {
@@ -7246,7 +7332,7 @@
"description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-client/tree/v6.0.1"
+ "source": "https://github.com/symfony/http-client/tree/v6.0.2"
},
"funding": [
{
@@ -7262,7 +7348,7 @@
"type": "tidelift"
}
],
- "time": "2021-12-08T15:13:44+00:00"
+ "time": "2021-12-29T10:14:09+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -7344,16 +7430,16 @@
},
{
"name": "symfony/process",
- "version": "v6.0.0",
+ "version": "v6.0.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "d970c45c2186aa4331d1656950a82df64e232580"
+ "reference": "71da2b7f3fdba460fcf61a97c8d3d14bbf3391ad"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/d970c45c2186aa4331d1656950a82df64e232580",
- "reference": "d970c45c2186aa4331d1656950a82df64e232580",
+ "url": "https://api.github.com/repos/symfony/process/zipball/71da2b7f3fdba460fcf61a97c8d3d14bbf3391ad",
+ "reference": "71da2b7f3fdba460fcf61a97c8d3d14bbf3391ad",
"shasum": ""
},
"require": {
@@ -7385,7 +7471,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v6.0.0"
+ "source": "https://github.com/symfony/process/tree/v6.0.2"
},
"funding": [
{
@@ -7401,7 +7487,7 @@
"type": "tidelift"
}
],
- "time": "2021-11-28T15:34:37+00:00"
+ "time": "2021-12-27T21:05:08+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/config/bundles.php b/config/bundles.php
index 49d3fb6..0a9c74a 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -2,4 +2,5 @@
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
+ Freddie\FreddieBundle::class => ['all' => true],
];
diff --git a/config/services.yaml b/config/services.yaml
deleted file mode 100644
index dca119f..0000000
--- a/config/services.yaml
+++ /dev/null
@@ -1,97 +0,0 @@
-parameters:
- env(TRANSPORT_DSN): 'php://default'
- env(ALLOW_ANONYMOUS): true
- env(JWT_SECRET_KEY): "!ChangeMe!"
- env(JWT_PUBLIC_KEY): ~
- env(JWT_ALGORITHM): "HS256"
- transport_dsn: '%env(TRANSPORT_DSN)%'
- allow_anonymous: '%env(bool:ALLOW_ANONYMOUS)%'
-
-services:
- _instanceof:
- Freddie\Hub\HubControllerInterface:
- tags: ['mercure.controller']
- Freddie\Hub\Transport\TransportFactoryInterface:
- tags: ['mercure.transport_factory']
- Lcobucci\JWT\Validation\Constraint:
- tags: ['lcobucci.jwt.validation_constraint']
-
- _defaults:
- autowire: true
- autoconfigure: true
- bind:
- iterable $controllers: !tagged_iterator mercure.controller
-
-
- Freddie\:
- resource: '%kernel.project_dir%/src/'
- exclude:
- - '%kernel.project_dir%/src/Hub/Transport/Redis/RedisPublisher.php'
- - '%kernel.project_dir%/src/Hub/Transport/Redis/RedisListener.php'
- - '%kernel.project_dir%/src/Hub/Transport/Redis/RedisTransport.php'
- - '%kernel.project_dir%/src/Kernel.php'
- - '%kernel.project_dir%/src/functions.php'
-
- Freddie\Hub\Controller\SubscribeController:
- arguments:
- $options:
- allow_anonymous: '%allow_anonymous%'
-
- Freddie\Security\JWT\Extractor\PSR7TokenExtractorInterface: '@Freddie\Security\JWT\Extractor\ChainTokenExtractor'
- Freddie\Hub\Transport\TransportFactory:
- arguments:
- $factories: !tagged_iterator mercure.transport_factory
-
- Freddie\Hub\Transport\TransportInterface:
- factory: ['@Freddie\Hub\Transport\TransportFactory', 'create']
- arguments: ['%transport_dsn%']
-
- Freddie\Security\JWT\Configuration\ValidationConstraints:
- arguments:
- - !tagged_iterator lcobucci.jwt.validation_constraint
-
- FrameworkX\App:
- arguments:
- - '@Freddie\Hub\Middleware\HttpExceptionConverterMiddleware'
- - '@Freddie\Hub\Middleware\TokenExtractorMiddleware'
-
- Evenement\EventEmitter: ~
- Evenement\EventEmitterInterface: '@Evenement\EventEmitter'
- Clue\React\Redis\Factory: ~
-
- Lcobucci\JWT\Configuration:
- factory: '@Freddie\Security\JWT\Configuration\ConfigurationFactory'
- arguments:
- - '%env(JWT_ALGORITHM)%'
- - '%env(resolve:JWT_SECRET_KEY)%'
- - '%env(string:default::resolve:JWT_PUBLIC_KEY)%'
- - '%env(string:default::JWT_PASSPHRASE)%'
-
- Lcobucci\JWT\Parser:
- factory: ['@Lcobucci\JWT\Configuration', 'parser']
-
- Lcobucci\JWT\Validator:
- factory: ['@Lcobucci\JWT\Configuration', 'validator']
-
- jwt.verification_key:
- class: Lcobucci\JWT\Signer\Key
- factory: '@Freddie\Security\JWT\Configuration\VerificationKeyFactory'
-
- DateTimeZone:
- class: DateTimeZone
- factory: '@Freddie\Security\JWT\Configuration\DateTimeZoneFactory'
-
- Lcobucci\Clock\SystemClock:
- arguments:
- - '@DateTimeZone'
-
- Lcobucci\Clock\Clock: '@Lcobucci\Clock\SystemClock'
-
- Lcobucci\JWT\Signer:
- factory: '@Freddie\Security\JWT\Configuration\SignerFactory'
-
- Lcobucci\JWT\Validation\Constraint\LooseValidAt: ~
- Lcobucci\JWT\Validation\Constraint\SignedWith:
- arguments:
- $key: '@jwt.verification_key'
-
diff --git a/phpstan.neon b/phpstan.neon
index 1ff472c..9f62798 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -2,3 +2,6 @@ parameters:
level: 8
paths:
- src
+
+ ignoreErrors:
+ - '#React\\Promise\\PromiseInterface is not generic#'
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index f411a25..9d57607 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -30,5 +30,8 @@
src
+
+ src/DependencyInjection
+
diff --git a/src/Command/ServeCommand.php b/src/Command/ServeCommand.php
index 65d9515..9474601 100644
--- a/src/Command/ServeCommand.php
+++ b/src/Command/ServeCommand.php
@@ -14,7 +14,9 @@
use function sprintf;
#[AsCommand(
- name: 'serve'
+ name: 'freddie:serve',
+ description: 'Start Freddie server, the PHP Mercure Hub.',
+ aliases: ['serve'],
)]
final class ServeCommand extends Command
{
diff --git a/src/DependencyInjection/FreddieExtension.php b/src/DependencyInjection/FreddieExtension.php
new file mode 100644
index 0000000..40fe1c9
--- /dev/null
+++ b/src/DependencyInjection/FreddieExtension.php
@@ -0,0 +1,22 @@
+ $configs
+ */
+ public function load(array $configs, ContainerBuilder $container): void
+ {
+ $loader = new PhpFileLoader($container, new FileLocator(__DIR__));
+ $loader->load('services.php');
+ }
+}
diff --git a/src/DependencyInjection/services.php b/src/DependencyInjection/services.php
new file mode 100644
index 0000000..261c357
--- /dev/null
+++ b/src/DependencyInjection/services.php
@@ -0,0 +1,174 @@
+parameters();
+ $params->set('env(TRANSPORT_DSN)', 'php://default');
+ $params->set('env(ALLOW_ANONYMOUS)', Hub::DEFAULT_OPTIONS['allow_anonymous']);
+ $params->set('env(JWT_SECRET_KEY)', '!ChangeMe!');
+ $params->set('env(JWT_PUBLIC_KEY)', null);
+ $params->set('env(JWT_ALGORITHM)', 'HS256');
+ $params->set('transport_dsn', '%env(resolve:TRANSPORT_DSN)%');
+ $params->set('allow_anonymous', '%env(bool:ALLOW_ANONYMOUS)%');
+
+ $services = $container->services();
+ $services
+ ->defaults()
+ ->private()
+ ->autoconfigure()
+ ->autowire()
+ ->bind('iterable $controllers', tagged_iterator('mercure.controller'))
+ ;
+
+ $services
+ ->instanceof(HubControllerInterface::class)
+ ->tag('mercure.controller');
+
+ $services
+ ->instanceof(TransportFactoryInterface::class)
+ ->tag('mercure.transport_factory');
+
+ $services
+ ->instanceof(Constraint::class)
+ ->tag('lcobucci.jwt.validation_constraint');
+
+ $services
+ ->load('Freddie\\', dirname(__DIR__))
+ ->exclude([
+ dirname(__DIR__) . '/Hub/DependencyInjection/*',
+ dirname(__DIR__) . '/Hub/Transport/Redis/RedisPublisher.php',
+ dirname(__DIR__) . '/Hub/Transport/Redis/RedisListener.php',
+ dirname(__DIR__) . '/Hub/Transport/Redis/RedisTransport.php',
+ dirname(__DIR__) . '/FreddieBundle.php',
+ dirname(__DIR__) . '/functions.php',
+ dirname(__DIR__) . '/Kernel.php',
+ ]);
+
+ $services
+ ->set(SubscribeController::class);
+
+ $services
+ ->alias(PSR7TokenExtractorInterface::class, ChainTokenExtractor::class);
+
+ $services
+ ->set(TransportFactory::class)
+ ->arg('$factories', tagged_iterator('mercure.transport_factory'));
+
+ $services
+ ->set(TransportInterface::class)
+ ->factory([service(TransportFactory::class), 'create'])
+ ->arg('$dsn', param('transport_dsn'));
+
+ $services
+ ->set(Hub::class)
+ ->arg('$options', ['allow_anonymous' => param('allow_anonymous')]);
+
+ $services->alias(HubInterface::class, Hub::class);
+
+ $services
+ ->set(ValidationConstraints::class)
+ ->arg('$validationConstraints', tagged_iterator('lcobucci.jwt.validation_constraint'));
+
+ $services
+ ->set(App::class)
+ ->args([
+ service(HttpExceptionConverterMiddleware::class),
+ service(TokenExtractorMiddleware::class),
+ ]);
+
+ $services
+ ->set(EventEmitter::class);
+
+ $services
+ ->alias(EventEmitterInterface::class, EventEmitter::class);
+
+ $services
+ ->set(Factory::class);
+
+ $services
+ ->set(Configuration::class)
+ ->factory(service(ConfigurationFactory::class))
+ ->args([
+ param('env(JWT_ALGORITHM)'),
+ param('env(resolve:JWT_SECRET_KEY)'),
+ param('env(string:default::resolve:JWT_PUBLIC_KEY)'),
+ param('env(string:default::JWT_PASSPHRASE)'),
+ ]);
+
+ $services
+ ->set(Parser::class)
+ ->factory([service(Configuration::class), 'parser']);
+
+ $services
+ ->set(Validator::class)
+ ->factory([service(Configuration::class), 'validator']);
+
+ $services
+ ->set('jwt.verification_key')
+ ->class(Key::class)
+ ->factory(service(VerificationKeyFactory::class));
+
+ $services
+ ->set(DateTimeZone::class)
+ ->class(DateTimeZone::class)
+ ->factory(service(DateTimeZoneFactory::class));
+
+ $services
+ ->set(SystemClock::class)
+ ->args([
+ service(DateTimeZone::class),
+ ]);
+
+ $services
+ ->alias(Clock::class, SystemClock::class);
+
+ $services
+ ->set(Signer::class)
+ ->factory(service(SignerFactory::class));
+
+ $services
+ ->set(Constraint\LooseValidAt::class);
+
+ $services
+ ->set(Constraint\SignedWith::class)
+ ->arg('$key', service('jwt.verification_key'));
+};
diff --git a/src/FreddieBundle.php b/src/FreddieBundle.php
new file mode 100644
index 0000000..c0d1aa2
--- /dev/null
+++ b/src/FreddieBundle.php
@@ -0,0 +1,11 @@
+hub = $hub;
+
+ return $this;
}
/**
@@ -66,7 +69,7 @@ public function __invoke(ServerRequestInterface $request): ResponseInterface
throw new AccessDeniedHttpException('Your rights are not sufficient to publish this update.');
}
- $this->transport->publish($update);
+ $this->hub->publish($update);
return new Response(201, body: (string) $update->message->id);
}
diff --git a/src/Hub/Controller/SubscribeController.php b/src/Hub/Controller/SubscribeController.php
index 200b4cf..3975d82 100644
--- a/src/Hub/Controller/SubscribeController.php
+++ b/src/Hub/Controller/SubscribeController.php
@@ -6,8 +6,7 @@
use Freddie\Helper\FlatQueryParser;
use Freddie\Hub\HubControllerInterface;
-use Freddie\Hub\Transport\PHP\PHPTransport;
-use Freddie\Hub\Transport\TransportInterface;
+use Freddie\Hub\HubInterface;
use Freddie\Message\Update;
use Lcobucci\JWT\UnencryptedToken;
use Psr\Http\Message\ResponseInterface;
@@ -18,7 +17,6 @@
use React\Stream\WritableStreamInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
-use Symfony\Component\OptionsResolver\OptionsResolver;
use function Freddie\extract_last_event_id;
use function BenTools\QueryString\query_string;
@@ -26,22 +24,13 @@
final class SubscribeController implements HubControllerInterface
{
- /**
- * @var array
- */
- private array $options;
+ private HubInterface $hub;
- /**
- * @param array $options
- */
- public function __construct(
- array $options = [],
- private TransportInterface $transport = new PHPTransport(),
- ) {
- $resolver = new OptionsResolver();
- $resolver->setRequired('allow_anonymous');
- $resolver->setAllowedTypes('allow_anonymous', 'bool');
- $this->options = $resolver->resolve($options);
+ public function setHub(HubInterface $hub): self
+ {
+ $this->hub = $hub;
+
+ return $this;
}
/**
@@ -71,20 +60,20 @@ public function __invoke(
if (null !== $lastEventId) {
async(
function () use ($lastEventId, $stream, $subscribedTopics, $allowedTopics) {
- foreach ($this->transport->reconciliate($lastEventId) as $update) {
+ foreach ($this->hub->reconciliate($lastEventId) as $update) {
$this->sendUpdate($update, $stream, $subscribedTopics, $allowedTopics);
}
}
- );
+ )();
}
async(
function () use ($stream, $subscribedTopics, $allowedTopics) {
- $this->transport->subscribe(function (Update $update) use ($stream, $subscribedTopics, $allowedTopics) {
+ $this->hub->subscribe(function (Update $update) use ($stream, $subscribedTopics, $allowedTopics) {
$this->sendUpdate($update, $stream, $subscribedTopics, $allowedTopics);
});
}
- );
+ )();
return new Response(
200,
@@ -103,7 +92,7 @@ private function sendUpdate(
array $subscribedTopics,
?array $allowedTopics,
): void {
- if (!$update->canBeReceived($subscribedTopics, $allowedTopics, $this->options['allow_anonymous'])) {
+ if (!$update->canBeReceived($subscribedTopics, $allowedTopics, $this->hub->getOption('allow_anonymous'))) {
return;
}
@@ -131,7 +120,7 @@ private function extractAllowedTopics(ServerRequestInterface $request): ?array
/** @var UnencryptedToken|null $jwt */
$jwt = $request->getAttribute('token');
if (null === $jwt) {
- if (!$this->options['allow_anonymous']) {
+ if (!$this->hub->getOption('allow_anonymous')) {
throw new AccessDeniedHttpException('Anonymous subscriptions are not allowed on this hub.');
}
diff --git a/src/Hub/Hub.php b/src/Hub/Hub.php
index 44265e1..e2f7827 100644
--- a/src/Hub/Hub.php
+++ b/src/Hub/Hub.php
@@ -6,18 +6,48 @@
use FrameworkX\App;
use Freddie\Hub\Middleware\HttpExceptionConverterMiddleware;
+use Freddie\Hub\Transport\PHP\PHPTransport;
+use Freddie\Hub\Transport\TransportInterface;
+use Freddie\Message\Update;
+use Generator;
+use InvalidArgumentException;
+use React\EventLoop\Loop;
+use React\Promise\PromiseInterface;
+use Symfony\Component\OptionsResolver\OptionsResolver;
-final class Hub
+use function array_key_exists;
+use function sprintf;
+
+final class Hub implements HubInterface
{
+ public const DEFAULT_OPTIONS = [
+ 'allow_anonymous' => true,
+ ];
+
+ /**
+ * @var array
+ */
+ private array $options;
+
+ private bool $started = false;
+
/**
* @codeCoverageIgnore
+ * @param array $options
* @param iterable $controllers
*/
public function __construct(
private App $app = new App(new HttpExceptionConverterMiddleware()),
+ private TransportInterface $transport = new PHPTransport(),
+ array $options = [],
iterable $controllers = [],
) {
+ $resolver = new OptionsResolver();
+ $resolver->setDefaults(self::DEFAULT_OPTIONS);
+ $resolver->setAllowedTypes('allow_anonymous', 'bool');
+ $this->options = $resolver->resolve($options);
foreach ($controllers as $controller) {
+ $controller->setHub($this);
$method = $controller->getMethod();
$route = $controller->getRoute();
$this->app->{$method}($route, $controller);
@@ -29,6 +59,38 @@ public function __construct(
*/
public function run(): void
{
+ $this->started = true;
$this->app->run();
}
+
+ public function publish(Update $update): PromiseInterface
+ {
+ return $this->transport->publish($update)
+ ->then(function (Update $update) {
+ if (false === $this->started) {
+ Loop::stop();
+ }
+
+ return $update;
+ });
+ }
+
+ public function subscribe(callable $callback): void
+ {
+ $this->transport->subscribe($callback);
+ }
+
+ public function reconciliate(string $lastEventID): Generator
+ {
+ return $this->transport->reconciliate($lastEventID);
+ }
+
+ public function getOption(string $name): mixed
+ {
+ if (!array_key_exists($name, $this->options)) {
+ throw new InvalidArgumentException(sprintf('Invalid option `%s`.', $name));
+ }
+
+ return $this->options[$name];
+ }
}
diff --git a/src/Hub/HubControllerInterface.php b/src/Hub/HubControllerInterface.php
index 8247c78..8e2da12 100644
--- a/src/Hub/HubControllerInterface.php
+++ b/src/Hub/HubControllerInterface.php
@@ -11,5 +11,6 @@ interface HubControllerInterface
{
public function getMethod(): string;
public function getRoute(): string;
+ public function setHub(HubInterface $hub): self;
public function __invoke(ServerRequestInterface $request): ResponseInterface;
}
diff --git a/src/Hub/HubInterface.php b/src/Hub/HubInterface.php
new file mode 100644
index 0000000..0e3b081
--- /dev/null
+++ b/src/Hub/HubInterface.php
@@ -0,0 +1,12 @@
+store($update);
$this->eventEmitter->emit('mercureUpdate', [$update]);
+
+ return resolve($update);
}
public function subscribe(callable $callback): void
diff --git a/src/Hub/Transport/Redis/RedisTransport.php b/src/Hub/Transport/Redis/RedisTransport.php
index 39a6ea8..2ad3f3d 100644
--- a/src/Hub/Transport/Redis/RedisTransport.php
+++ b/src/Hub/Transport/Redis/RedisTransport.php
@@ -9,8 +9,10 @@
use Freddie\Message\Update;
use Generator;
use React\EventLoop\Loop;
+use React\Promise\PromiseInterface;
use function React\Async\await;
+use function React\Promise\resolve;
final class RedisTransport implements TransportInterface
{
@@ -35,12 +37,14 @@ public function subscribe(callable $callback): void
});
}
- public function publish(Update $update): void
+ public function publish(Update $update): PromiseInterface
{
$this->init();
$payload = $this->serializer->serialize($update);
- $this->redis->publish($this->channel, $payload); // @phpstan-ignore-line
- $this->store($update);
+
+ return $this->redis->publish($this->channel, $payload) // @phpstan-ignore-line
+ ->then(fn() => $this->store($update))
+ ->then(fn() => $update);
}
public function reconciliate(string $lastEventID): Generator
@@ -63,14 +67,14 @@ public function reconciliate(string $lastEventID): Generator
}
}
- private function store(Update $update): void
+ private function store(Update $update): PromiseInterface
{
$this->init();
if ($this->size <= 0) {
- return;
+ return resolve();
}
- $this->redis->rpush($this->storageKey, $this->serializer->serialize($update)); // @phpstan-ignore-line
+ return $this->redis->rpush($this->storageKey, $this->serializer->serialize($update)); // @phpstan-ignore-line
}
private function init(): void
diff --git a/src/Hub/Transport/TransportInterface.php b/src/Hub/Transport/TransportInterface.php
index d0d7bf4..189d0ec 100644
--- a/src/Hub/Transport/TransportInterface.php
+++ b/src/Hub/Transport/TransportInterface.php
@@ -6,12 +6,16 @@
use Freddie\Message\Update;
use Generator;
+use React\Promise\PromiseInterface;
interface TransportInterface
{
public const EARLIEST = 'earliest';
- public function publish(Update $update): void;
+ /**
+ * @return PromiseInterface
+ */
+ public function publish(Update $update): PromiseInterface;
public function subscribe(callable $callback): void;
diff --git a/symfony.lock b/symfony.lock
index 5d6f238..6e2dde9 100644
--- a/symfony.lock
+++ b/symfony.lock
@@ -323,6 +323,9 @@
"symfony/options-resolver": {
"version": "v6.0.0-RC1"
},
+ "symfony/polyfill-ctype": {
+ "version": "v1.24.0"
+ },
"symfony/polyfill-intl-grapheme": {
"version": "v1.23.1"
},
diff --git a/tests/Unit/Hub/Controller/PublishControllerTest.php b/tests/Unit/Hub/Controller/PublishControllerTest.php
index 137a020..98d9a9f 100644
--- a/tests/Unit/Hub/Controller/PublishControllerTest.php
+++ b/tests/Unit/Hub/Controller/PublishControllerTest.php
@@ -6,6 +6,7 @@
use FrameworkX\App;
use Freddie\Hub\Controller\PublishController;
+use Freddie\Hub\Hub;
use Freddie\Hub\Middleware\HttpExceptionConverterMiddleware;
use Freddie\Hub\Middleware\TokenExtractorMiddleware;
use Freddie\Hub\Transport\PHP\PHPTransport;
@@ -31,7 +32,7 @@
?Update $expectedUpdate
) {
$transport = new PHPTransport(size: 1);
- $controller = new PublishController($transport);
+ $controller = new PublishController();
$app = new App(
new TokenExtractorMiddleware(
jwt_config()->parser(),
@@ -40,6 +41,8 @@
new HttpExceptionConverterMiddleware(),
$controller,
);
+ $hub = new Hub($app, $transport);
+ $controller->setHub($hub);
$transportRefl = new ReflectionClass($transport);
$updates = $transportRefl->getProperty('updates');
$updates->setAccessible(true);
diff --git a/tests/Unit/Hub/Controller/SubscribeControllerTest.php b/tests/Unit/Hub/Controller/SubscribeControllerTest.php
index 231c2aa..a79be72 100644
--- a/tests/Unit/Hub/Controller/SubscribeControllerTest.php
+++ b/tests/Unit/Hub/Controller/SubscribeControllerTest.php
@@ -6,6 +6,7 @@
use FrameworkX\App;
use Freddie\Hub\Controller\SubscribeController;
+use Freddie\Hub\Hub;
use Freddie\Hub\Middleware\HttpExceptionConverterMiddleware;
use Freddie\Hub\Transport\PHP\PHPTransport;
use Freddie\Message\Message;
@@ -20,11 +21,8 @@
it('receives updates and dumps them into the stream', function () {
$transport = new PHPTransport(size: 1000);
- $controller = new SubscribeController(['allow_anonymous' => true], $transport);
- $app = new App(
- new HttpExceptionConverterMiddleware(),
- $controller,
- );
+ $controller = new SubscribeController();
+ $controller->setHub(new Hub(transport: $transport));
$stream = new ThroughStreamStub();
// Given
@@ -57,7 +55,8 @@
it('receives private updates when authorized', function () {
$transport = new PHPTransport(size: 1000);
- $controller = new SubscribeController(['allow_anonymous' => true], $transport);
+ $controller = new SubscribeController();
+ $controller->setHub(new Hub(transport: $transport));
$stream = new ThroughStreamStub();
// Given
@@ -97,7 +96,8 @@
});
it('yells if user doesn\'t subscribe to at least one topic', function () {
- $controller = new SubscribeController(['allow_anonymous' => true]);
+ $controller = new SubscribeController();
+ $controller->setHub(new Hub(options: ['allow_anonymous' => true]));
// Given
$request = new ServerRequest(
@@ -116,7 +116,8 @@
);
it('yells when anonymous subscriptions are forbidden and user doesn\'t provide a JWT', function () {
- $controller = new SubscribeController(['allow_anonymous' => false]);
+ $controller = new SubscribeController();
+ $controller->setHub(new Hub(options: ['allow_anonymous' => false]));
// Given
$request = new ServerRequest(
@@ -135,7 +136,8 @@
);
it('complains if JWT is invalid', function () {
- $controller = new SubscribeController(['allow_anonymous' => false]);
+ $controller = new SubscribeController();
+ $controller->setHub(new Hub(options: ['allow_anonymous' => false]));
// Given
$jwt = create_jwt(['mercure' => ['publish' => ['*']]]) . 'foo';
diff --git a/tests/Unit/Hub/HubTest.php b/tests/Unit/Hub/HubTest.php
new file mode 100644
index 0000000..98738d6
--- /dev/null
+++ b/tests/Unit/Hub/HubTest.php
@@ -0,0 +1,62 @@
+called['publish'] = func_get_args();
+
+ return resolve($update);
+ }
+
+ public function subscribe(callable $callback): void
+ {
+ $this->called['subscribe'] = func_get_args();
+ }
+
+ public function reconciliate(string $lastEventID): Generator
+ {
+ $this->called['reconciliate'] = func_get_args();
+ yield;
+ }
+ };
+
+ // Given
+ $hub = new Hub(transport: $transport);
+ $update = new Update(['foo'], new Message(Ulid::generate()));
+ $subscribeFn = fn () => 'bar';
+ $lastEventId = Ulid::generate();
+
+ // When
+ $hub->publish($update);
+ $hub->subscribe($subscribeFn);
+ iterator_to_array($hub->reconciliate($lastEventId));
+
+ // Then
+ expect($transport->called['publish'])->toBe([$update]);
+ expect($transport->called['subscribe'])->toBe([$subscribeFn]);
+ expect($transport->called['reconciliate'])->toBe([$lastEventId]);
+});
+
+it('complains when requesting an unrecognized option', function () {
+ $hub = new Hub();
+ $hub->getOption('foo');
+})->throws(InvalidArgumentException::class, 'Invalid option `foo`.');
diff --git a/tests/Unit/Hub/Transport/Redis/RedisClientStub.php b/tests/Unit/Hub/Transport/Redis/RedisClientStub.php
index a84a8c4..1485c00 100644
--- a/tests/Unit/Hub/Transport/Redis/RedisClientStub.php
+++ b/tests/Unit/Hub/Transport/Redis/RedisClientStub.php
@@ -8,13 +8,14 @@
use Clue\React\Redis\Client;
use Evenement\EventEmitter;
use Evenement\EventEmitterInterface;
-use Evenement\EventEmitterTrait;
use Pest\Exceptions\ShouldNotHappen;
+use React\Promise\PromiseInterface;
use function abs;
use function array_splice;
use function count;
use function React\Async\async;
+use function React\Promise\resolve;
final class RedisClientStub implements Client
{
@@ -31,16 +32,20 @@ public function subscribe(string $channel): void
$this->subscribedChannels[] = $channel;
}
- public function publish(string $channel, string $payload): void
+ public function publish(string $channel, string $payload): PromiseInterface
{
$this->emit('message', [$channel, $payload]);
+
+ return resolve(true);
}
- public function rpush(string $key, string ...$items): void
+ public function rpush(string $key, string ...$items): PromiseInterface
{
foreach ($items as $item) {
$this->storage[$key][] = $item;
}
+
+ return resolve(true);
}
public function lrange(string $key, int $from, int $to)
@@ -54,12 +59,12 @@ public function lrange(string $key, int $from, int $to)
$length -= $from;
- return async(fn () => array_splice($items, $firstIndex, $length));
+ return async(fn () => array_splice($items, $firstIndex, $length))();
}
public function ltrim(string $key, int $from, int $to)
{
- return async(fn () => $this->lrange($key, $from, $to))
+ return async(fn () => $this->lrange($key, $from, $to))()
->then(fn (array $items) => $this->storage[$key] = $items);
}