diff --git a/.github/workflows/split_monorepo.yml b/.github/workflows/split_monorepo.yml index b50af0112..602eea909 100644 --- a/.github/workflows/split_monorepo.yml +++ b/.github/workflows/split_monorepo.yml @@ -38,6 +38,8 @@ jobs: split_repository: 'auth-http' - local_path: 'Boot' split_repository: 'boot' + - local_path: 'Broadcasting' + split_repository: 'broadcasting' - local_path: 'Cache' split_repository: 'cache' - local_path: 'Config' diff --git a/CHANGELOG.md b/CHANGELOG.md index b0c35563e..ef62ab6a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Please, use standalone package `spiral/data-grid-bridge` instead. - Component `spiral/data-grid` is removed from `spiral/framework` repository. Please, use standalone package `spiral/data-grid` instead. - - `Spiral\Boot\ExceptionHandler` has been eliminated. New `Spiral\Exceptions\ExceptionHandler` with interfaces + - `Spiral\Boot\ExceptionHandler` has been eliminated. New `Spiral\Exceptions\ExceptionHandler` with interfaces `Spiral\Exceptions\ExceptionHandlerInterface`, `Spiral\Exceptions\ExceptionRendererInterface` and `Spiral\Exceptions\ExceptionReporterInterface` have been added. - Added `ExceptionHandlerBootloader` that adds renderers and reporters in the `ExceptionHandler`. @@ -39,7 +39,7 @@ - Removed `Spiral\Http\SapiDispatcher` and `Spiral\Http\Emitter\SapiEmitter`. Please, use package `spiral/sapi-bridge` instead. - Bootloader `Spiral\Bootloader\Http\DiactorosBootloader` moved to the `Spiral\Http\Bootloader\DiactorosBootloader`. - Classes `Spiral\Http\Diactoros\ResponseFactory`, `Spiral\Http\Diactoros\ServerRequestFactory`, `Spiral\Http\Diactoros\StreamFactory`, - `Spiral\Http\Diactoros\UploadedFileFactory`, `Spiral\Http\Diactoros\UriFactory` + `Spiral\Http\Diactoros\UploadedFileFactory`, `Spiral\Http\Diactoros\UriFactory` is removed and replaced to a `Nyholm\Psr7\Factory\Psr17Factory` class from `nyholm/psr7` package in `DiactorosBootloader`. - [spiral/exceptions] All handlers have been renamed into renderers. `HandlerInterface` has been deleted. - [spiral/exceptions] Added `Spiral\Exceptions\Verbosity` enum. @@ -111,7 +111,7 @@ - [spiral/boot] `Spiral\Boot\AbstractKernel` constructor is protected now. - [spiral/boot] Added return type `mixed` to the method `loadData`, added return type `void` and `mixed` parameter type of `$data` to the method `saveData` in `Spiral\Boot\MemoryInterface` interface. - - [spiral/boot] In `Bootloaders`, the name of the method has been changed from `boot` to `init`. + - [spiral/boot] In `Bootloaders`, the name of the method has been changed from `boot` to `init`. In the code of custom Bootloaders, need to change the name of the method. - [spiral/console] Added return type `void` to the method `writeHeader`, added return type `void` to the method `execute`, method `whiteFooter` renamed to `writeFooter`, added return type `void` to the method `writeFooter` @@ -142,7 +142,7 @@ config `Spiral\Bootloader\Storage\StorageConfig` moved to the `Spiral\Storage\Config\StorageConfig`. - [spiral/validation] Bootloader `Spiral\Bootloader\Security\ValidationBootloader` moved to the `Spiral\Validation\Bootloader\ValidationBootloader`. - [spiral/views] Bootloader `Spiral\Bootloader\Views\ViewsBootloader` moved to the `Spiral\Views\Bootloader\ViewsBootloader`. - - [spiral/boot] By default, overwriting of environment variable values is disabled, the default value for `$overwrite` + - [spiral/boot] By default, overwriting of environment variable values is disabled, the default value for `$overwrite` changed from `true` to `false` in the `Spiral\Boot\Environment`. - **Medium Impact Changes** - A minimal version of `PHP` increased to `^8.1` @@ -156,11 +156,32 @@ - [spiral/debug] Added `Spiral\Debug\StateConsumerInterface`. - [spiral/boot] Added new `boot` method in `Bootloaders`. It will be executed after the `init` method is executed in all `Bootloaders`. The old `boot` method has been renamed to `init`. See **High Impact Changes** section. - - [spiral/boot] Added automatic booting of `Bootloaders` requested in the `init` and `boot` methods. + - [spiral/boot] Added automatic booting of `Bootloaders` requested in the `init` and `boot` methods. They no longer need to be specified explicitly in `DEPENDENCIES` property or in `defineDependencies` method. - - [spiral/monolog-bridge] Added the ability to configure the default channel using the configuration file or + - [spiral/monolog-bridge] Added the ability to configure the default channel using the configuration file or environment variable `MONOLOG_DEFAULT_CHANNEL`. - + +## v2.14.0 - Unreleased +- **High Impact Changes** +- **Medium Impact Changes** +- **Low Impact Changes** +- **Other Features** +- **Bug Fixes** + +## v2.13.0 - 2022-04-28 +- **Medium Impact Changes** + - Dispatcher `Spiral\Http\SapiDispatcher` is deprecated. Will be moved to `spiral/sapi-bridge` and removed in v3.0 + - Classes `Spiral\Http\Emitter\SapiEmitter`, `Spiral\Http\Exception\EmitterException`, `Spiral\Http\EmitterInterface`, + `Spiral\Http\SapiRequestFactory` is deprecated. Will be removed in version v3.0. + After the release of v3.0, must use the package `spiral/sapi-bridge` for SAPI functionality. + - The `dumper` component is deprecated and will be removed in v3.0 +- **Other Features** + - [spiral/http] Added parameter `chunkSize` in the `http` configuration file. + - [spiral/queue] Added attribute `Queueable` to mark classes that can be queued. + Added `Spiral\Queue\QueueableDetector` class to easily check if an object should be queued or not and get the queue + from an attribute or getQueue method on the object. + - [spiral/broadcasting] New component with common interfaces (RR2.0 support) + ## v2.12.0 - 2022-04-07 - **Medium Impact Changes** - Bootloaders `Spiral\Bootloader\Broadcast\BroadcastBootloader`, `Spiral\Bootloader\Http\WebsocketsBootloader` diff --git a/composer.json b/composer.json index 15d917d25..89b3a1943 100644 --- a/composer.json +++ b/composer.json @@ -68,6 +68,7 @@ "src/AuthHttp/src" ], "Spiral\\Boot\\": "src/Boot/src", + "Spiral\\Broadcasting\\": "src/Broadcasting/src", "Spiral\\Cache\\": "src/Cache/src", "Spiral\\Config\\": "src/Config/src", "Spiral\\Console\\": "src/Console/src", @@ -133,7 +134,7 @@ "spiral/code-style": "^1.0", "symfony/var-dumper": "^5.2|^6.0", "symplify/monorepo-builder": "^10.0", - "vimeo/psalm": "^4.22" + "vimeo/psalm": "4.21" }, "autoload-dev": { "psr-4": { @@ -143,6 +144,7 @@ "src/Auth/tests", "src/AuthHttp/tests" ], + "Spiral\\Tests\\Broadcasting\\": "src/Broadcasting/tests", "Spiral\\Tests\\Boot\\": "src/Boot/tests", "Spiral\\Tests\\Cache\\": "src/Cache/tests", "Spiral\\Tests\\Config\\": "src/Config/tests", @@ -200,6 +202,7 @@ "spiral/auth": "self.version", "spiral/auth-http": "self.version", "spiral/boot": "self.version", + "spiral/broadcasting": "self.version", "spiral/cache": "self.version", "spiral/config": "self.version", "spiral/console": "self.version", diff --git a/monorepo-builder.php b/monorepo-builder.php index c31fce8de..549db74f3 100644 --- a/monorepo-builder.php +++ b/monorepo-builder.php @@ -89,7 +89,7 @@ 'spiral/code-style' => '^1.0', 'laminas/laminas-hydrator' => '^3.0|^4.0', 'symplify/monorepo-builder' => '^10.0', - 'vimeo/psalm' => '^4.22', + 'vimeo/psalm' => '^4.21', ], ]); diff --git a/src/AnnotatedRoutes/composer.json b/src/AnnotatedRoutes/composer.json index 36f72e754..a20b0731f 100644 --- a/src/AnnotatedRoutes/composer.json +++ b/src/AnnotatedRoutes/composer.json @@ -16,8 +16,8 @@ ], "require": { "php": ">=8.1", - "spiral/attributes": "^2.13", - "spiral/router": "^2.13" + "spiral/attributes": "^2.14", + "spiral/router": "^2.14" }, "autoload": { "psr-4": { @@ -28,8 +28,8 @@ "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5", "laminas/laminas-diactoros": "^2.8", - "spiral/boot": "^2.13", - "spiral/console": "^2.13" + "spiral/boot": "^2.14", + "spiral/console": "^2.14" }, "autoload-dev": { "psr-4": { @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Attributes/composer.json b/src/Attributes/composer.json index e25cd3641..1fd56d8ee 100644 --- a/src/Attributes/composer.json +++ b/src/Attributes/composer.json @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "scripts": { diff --git a/src/Auth/composer.json b/src/Auth/composer.json index fa0db7bc8..4e685b983 100644 --- a/src/Auth/composer.json +++ b/src/Auth/composer.json @@ -32,7 +32,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/AuthHttp/composer.json b/src/AuthHttp/composer.json index 66982ad1c..cebc58748 100644 --- a/src/AuthHttp/composer.json +++ b/src/AuthHttp/composer.json @@ -16,15 +16,15 @@ "require": { "php": ">=8.1", "ext-json": "*", - "spiral/auth": "^2.13", + "spiral/auth": "^2.14", "psr/http-message": "^1.0", "psr/http-server-middleware": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9.5.5", - "spiral/cookies": "^2.13", - "spiral/http": "^2.13", - "spiral/debug": "^2.13", + "spiral/cookies": "^2.14", + "spiral/http": "^2.14", + "spiral/debug": "^2.14", "laminas/laminas-diactoros": "^2.8" }, "autoload": { @@ -39,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Boot/composer.json b/src/Boot/composer.json index 36f738e1f..d6bd6de08 100644 --- a/src/Boot/composer.json +++ b/src/Boot/composer.json @@ -16,11 +16,11 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", - "spiral/files": "^2.13", - "spiral/config": "^2.13", - "spiral/debug": "^2.13", - "spiral/exceptions": "^2.13" + "spiral/core": "^2.14", + "spiral/files": "^2.14", + "spiral/config": "^2.14", + "spiral/debug": "^2.14", + "spiral/exceptions": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5", @@ -41,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Bridge/Dotenv/composer.json b/src/Bridge/Dotenv/composer.json index b07227b83..db2729177 100644 --- a/src/Bridge/Dotenv/composer.json +++ b/src/Bridge/Dotenv/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=8.1", - "spiral/boot": "^2.13", + "spiral/boot": "^2.14", "vlucas/phpdotenv": "^5.4" }, "require-dev": { @@ -34,7 +34,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Bridge/Monolog/composer.json b/src/Bridge/Monolog/composer.json index 9fedc8f36..8a753c4e1 100644 --- a/src/Bridge/Monolog/composer.json +++ b/src/Bridge/Monolog/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=8.1", - "spiral/boot": "^2.13", + "spiral/boot": "^2.14", "monolog/monolog": "^2.2" }, "require-dev": { @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Bridge/Stempler/composer.json b/src/Bridge/Stempler/composer.json index d011f8cf2..f6754f8ed 100644 --- a/src/Bridge/Stempler/composer.json +++ b/src/Bridge/Stempler/composer.json @@ -16,19 +16,19 @@ ], "require": { "php": ">=8.1", - "spiral/stempler": "^2.13", - "spiral/boot": "^2.13", - "spiral/files": "^2.13", - "spiral/config": "^2.13", - "spiral/translator": "^2.13", - "spiral/router": "^2.13", - "spiral/views": "^2.13", - "spiral/core": "^2.13" + "spiral/stempler": "^2.14", + "spiral/boot": "^2.14", + "spiral/files": "^2.14", + "spiral/config": "^2.14", + "spiral/translator": "^2.14", + "spiral/router": "^2.14", + "spiral/views": "^2.14", + "spiral/core": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5", - "spiral/dumper": "^2.13" + "spiral/dumper": "^2.14" }, "autoload": { "psr-4": { @@ -42,7 +42,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Broadcasting/.gitattributes b/src/Broadcasting/.gitattributes new file mode 100644 index 000000000..f68d9df1d --- /dev/null +++ b/src/Broadcasting/.gitattributes @@ -0,0 +1,7 @@ +/.github export-ignore +/tests export-ignore +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.travis.yml export-ignore +phpunit.xml export-ignore diff --git a/src/Broadcasting/.gitignore b/src/Broadcasting/.gitignore new file mode 100644 index 000000000..5b340c146 --- /dev/null +++ b/src/Broadcasting/.gitignore @@ -0,0 +1,19 @@ +# IDEA +.idea/ +*.iml + +# Composer +vendor/ +composer.lock +composer.phar + +# OS +.DS_Store +Thumbs.db + +# Other +.phpunit.result.cache +.php_cs.cache +clover.xml +.env +builds diff --git a/src/Broadcasting/LICENSE b/src/Broadcasting/LICENSE new file mode 100644 index 000000000..80ffcbeb1 --- /dev/null +++ b/src/Broadcasting/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Spiral Scout + +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. \ No newline at end of file diff --git a/src/Broadcasting/README.md b/src/Broadcasting/README.md new file mode 100644 index 000000000..df7aaf227 --- /dev/null +++ b/src/Broadcasting/README.md @@ -0,0 +1,5 @@ +# Spiral: Common Broadcasting Interfaces + +[![Latest Stable Version](https://poser.pugx.org/spiral/broadcasting/version)](https://packagist.org/packages/spiral/broadcasting) +[![Build Status](https://travis-ci.org/spiral/broadcasting.svg?branch=master)](https://travis-ci.org/spiral/broadcasting) +[![Codecov](https://codecov.io/gh/spiral/broadcasting/branch/master/graph/badge.svg)](https://codecov.io/gh/spiral/broadcasting/) diff --git a/src/Broadcasting/composer.json b/src/Broadcasting/composer.json new file mode 100644 index 000000000..75b11c0e4 --- /dev/null +++ b/src/Broadcasting/composer.json @@ -0,0 +1,54 @@ +{ + "name": "spiral/broadcasting", + "type": "library", + "description": "Common Broadcasting Interfaces", + "license": "MIT", + "homepage": "https://spiral.dev", + "support": { + "issues": "https://github.com/spiral/framework/issues", + "source": "https://github.com/spiral/core" + }, + "authors": [ + { + "name": "Anton Titov (wolfy-j)", + "email": "wolfy-j@spiralscout.com" + }, + { + "name": "Pavel Buchnev", + "email": "pavel.buchnev@spiralscout.com" + } + ], + "require": { + "php": ">=7.4", + "psr/http-message": "^1.0", + "psr/http-server-middleware": "^1.0", + "psr/log": "1 - 3", + "spiral/core": "^2.14", + "spiral/config": "^2.14" + }, + "autoload": { + "psr-4": { + "Spiral\\Broadcasting\\": "src" + } + }, + "require-dev": { + "spiral/boot": "^2.14", + "mockery/mockery": "^1.5", + "phpunit/phpunit": "^8.5|^9.5" + }, + "autoload-dev": { + "psr-4": { + "Spiral\\Tests\\Broadcasting\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.14.x-dev" + } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/src/Broadcasting/phpunit.xml b/src/Broadcasting/phpunit.xml new file mode 100644 index 000000000..a988141bb --- /dev/null +++ b/src/Broadcasting/phpunit.xml @@ -0,0 +1,28 @@ + + + + + tests + + + + + src + + + + + + + \ No newline at end of file diff --git a/src/Broadcasting/src/AuthorizationStatus.php b/src/Broadcasting/src/AuthorizationStatus.php new file mode 100644 index 000000000..d4516b6c7 --- /dev/null +++ b/src/Broadcasting/src/AuthorizationStatus.php @@ -0,0 +1,64 @@ +success = $success; + $this->topics = $topics; + $this->attributes = $attributes; + $this->response = $response; + } + + /** + * Check if authorization status is successful. + */ + public function isSuccessful(): bool + { + return $this->success; + } + + /** + * Get list of authorized topics. + */ + public function getTopics(): array + { + return $this->topics; + } + + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * Check if response is set. + */ + public function hasResponse(): bool + { + return $this->response !== null; + } + + /** + * Get response object. + */ + public function getResponse(): ?ResponseInterface + { + return $this->response; + } +} diff --git a/src/Broadcasting/src/Bootloader/BroadcastingBootloader.php b/src/Broadcasting/src/Bootloader/BroadcastingBootloader.php new file mode 100644 index 000000000..c5d841a87 --- /dev/null +++ b/src/Broadcasting/src/Bootloader/BroadcastingBootloader.php @@ -0,0 +1,81 @@ + BroadcastManager::class, + BroadcastInterface::class => [self::class, 'initDefaultBroadcast'], + TopicRegistryInterface::class => [self::class, 'initTopicRegistry'], + ]; + + private ConfiguratorInterface $config; + + public function __construct(ConfiguratorInterface $config) + { + $this->config = $config; + } + + public function registerDriverAlias(string $driverClass, string $alias): void + { + $this->config->modify( + BroadcastConfig::CONFIG, + new Append('driverAliases', $alias, $driverClass) + ); + } + + public function boot(EnvironmentInterface $env): void + { + $this->initConfig($env); + } + + private function initConfig(EnvironmentInterface $env): void + { + $this->config->setDefaults( + BroadcastConfig::CONFIG, + [ + 'default' => $env->get('BROADCAST_CONNECTION', 'null'), + 'authorize' => [ + 'path' => $env->get('BROADCAST_AUTHORIZE_PATH'), + 'topics' => [], + ], + 'aliases' => [], + 'connections' => [ + 'null' => [ + 'driver' => 'null', + ], + ], + 'driverAliases' => [ + 'null' => NullBroadcast::class, + 'log' => LogBroadcast::class, + ], + ] + ); + } + + private function initDefaultBroadcast(BroadcastManagerInterface $manager): BroadcastInterface + { + return $manager->connection(); + } + + private function initTopicRegistry(BroadcastConfig $config): TopicRegistryInterface + { + return new TopicRegistry($config->getTopics()); + } +} diff --git a/src/Broadcasting/src/Bootloader/WebsocketsBootloader.php b/src/Broadcasting/src/Bootloader/WebsocketsBootloader.php new file mode 100644 index 000000000..6b36514a1 --- /dev/null +++ b/src/Broadcasting/src/Bootloader/WebsocketsBootloader.php @@ -0,0 +1,42 @@ +bindSingleton(AuthorizationMiddleware::class, static function ( + BroadcastInterface $broadcast, + ResponseFactoryInterface $responseFactory, + BroadcastConfig $config + ): AuthorizationMiddleware { + return new AuthorizationMiddleware( + $broadcast, + $responseFactory, + $config->getAuthorizationPath() + ); + }); + + + if ($config->getAuthorizationPath() !== null) { + $http->addMiddleware(AuthorizationMiddleware::class); + } + } +} diff --git a/src/Broadcasting/src/BroadcastInterface.php b/src/Broadcasting/src/BroadcastInterface.php new file mode 100644 index 000000000..8c606d904 --- /dev/null +++ b/src/Broadcasting/src/BroadcastInterface.php @@ -0,0 +1,38 @@ + | non-empty-list | string | Stringable + * @psalm-type MessagesList = non-empty-list | non-empty-list | string | Stringable + */ +interface BroadcastInterface +{ + /** + * Method to send messages to the required topic (channel). + * + * $broadcast->publish('topic', 'message'); + * $broadcast->publish('topic', ['message 1', 'message 2']); + * + * $broadcast->publish(['topic 1', 'topic 2'], 'message'); + * $broadcast->publish(['topic 1', 'topic 2'], ['message 1', 'message 2']); + * + * + * Note: In future major releases, the signature of this method will be + * changed to include follow type-hints. + * + * + * public function publish(iterable|string|\Stringable $topics, iterable|string $messages): void; + * + * + * @param TopicsList $topics + * @param MessagesList $messages + * @throws BroadcastException + */ + public function publish($topics, $messages): void; +} diff --git a/src/Broadcasting/src/BroadcastManager.php b/src/Broadcasting/src/BroadcastManager.php new file mode 100644 index 000000000..cc2ea14ae --- /dev/null +++ b/src/Broadcasting/src/BroadcastManager.php @@ -0,0 +1,46 @@ +factory = $factory; + $this->config = $config; + } + + public function connection(?string $name = null): BroadcastInterface + { + $name = $name ?: $this->config->getDefaultConnection(); + + // Replaces alias with real storage name + $name = $this->config->getAliases()[$name] ?? $name; + + if (isset($this->connections[$name])) { + return $this->connections[$name]; + } + + return $this->connections[$name] = $this->resolve($name); + } + + private function resolve(string $name): BroadcastInterface + { + $config = $this->config->getConnectionConfig($name); + + return $this->factory->make($config['driver'], $config); + } +} diff --git a/src/Broadcasting/src/BroadcastManagerInterface.php b/src/Broadcasting/src/BroadcastManagerInterface.php new file mode 100644 index 000000000..2896e396d --- /dev/null +++ b/src/Broadcasting/src/BroadcastManagerInterface.php @@ -0,0 +1,14 @@ + [ + 'path' => null, + 'topics' => [], + ], + 'default' => 'null', + 'aliases' => [], + 'connections' => [], + 'driverAliases' => [], + ]; + + /** + * Get registerer broadcast topics. + * + * @return array + */ + public function getTopics(): array + { + return (array)($this->config['authorize']['topics'] ?? []); + } + + /** + * Get authorization path for broadcasting topics. + */ + public function getAuthorizationPath(): ?string + { + return $this->config['authorize']['path'] ?? null; + } + + /** + * Get broadcast driver aliases + */ + public function getAliases(): array + { + return (array)($this->config['aliases'] ?? []); + } + + /** + * Get default broadcast connection + */ + public function getDefaultConnection(): string + { + if (!isset($this->config['default']) || empty($this->config['default'])) { + throw new InvalidArgumentException('Default broadcast connection is not defined.'); + } + + if (!\is_string($this->config['default'])) { + throw new InvalidArgumentException('Default broadcast connection config value must be a string'); + } + + return $this->config['default']; + } + + public function getConnectionConfig(string $name): array + { + if (!isset($this->config['connections'][$name])) { + throw new InvalidArgumentException( + sprintf('Config for connection `%s` is not defined.', $name) + ); + } + + $config = $this->config['connections'][$name]; + + if (!isset($config['driver'])) { + throw new InvalidArgumentException( + sprintf('Driver for `%s` connection is not defined.', $name) + ); + } + + if (!\is_string($config['driver'])) { + throw new InvalidArgumentException( + \sprintf('Driver value for `%s` connection must be a string', $name) + ); + } + + if (isset($this->config['driverAliases'][$config['driver']])) { + $config['driver'] = $this->config['driverAliases'][$config['driver']]; + } + + return $config; + } +} diff --git a/src/Broadcasting/src/Driver/AbstractBroadcast.php b/src/Broadcasting/src/Driver/AbstractBroadcast.php new file mode 100644 index 000000000..ed162d666 --- /dev/null +++ b/src/Broadcasting/src/Driver/AbstractBroadcast.php @@ -0,0 +1,42 @@ +|T $entries + * @return array + */ + protected function toArray($entries): array + { + switch (true) { + case \is_array($entries): + return $entries; + + case $entries instanceof \Traversable: + return \iterator_to_array($entries, false); + + default: + return [$entries]; + } + } +} diff --git a/src/Broadcasting/src/Driver/LogBroadcast.php b/src/Broadcasting/src/Driver/LogBroadcast.php new file mode 100644 index 000000000..1470a50a2 --- /dev/null +++ b/src/Broadcasting/src/Driver/LogBroadcast.php @@ -0,0 +1,31 @@ +logger = $logger; + $this->level = $level; + } + + public function publish($topics, $messages): void + { + $topics = implode(', ', $this->formatTopics($this->toArray($topics))); + + /** @var string $message */ + foreach ($this->toArray($messages) as $message) { + assert(\is_string($message), 'Message argument must be a type of string'); + $this->logger->log($this->level, 'Broadcasting on channels [' . $topics . '] with payload: ' . $message); + } + } +} diff --git a/src/Broadcasting/src/Driver/NullBroadcast.php b/src/Broadcasting/src/Driver/NullBroadcast.php new file mode 100644 index 000000000..ca2b4c348 --- /dev/null +++ b/src/Broadcasting/src/Driver/NullBroadcast.php @@ -0,0 +1,15 @@ +responseFactory = $responseFactory; + $this->broadcast = $broadcast; + $this->authorizationPath = $authorizationPath; + } + + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + if ($request->getUri()->getPath() !== $this->authorizationPath) { + return $handler->handle($request); + } + + if ($this->broadcast instanceof GuardInterface) { + $status = $this->broadcast->authorize($request); + + if ($status->hasResponse()) { + return $status->getResponse(); + } + + if (!$status->isSuccessful()) { + return $this->responseFactory->createResponse(403); + } + } + + return $this->responseFactory->createResponse(200); + } +} diff --git a/src/Broadcasting/src/TopicRegistry.php b/src/Broadcasting/src/TopicRegistry.php new file mode 100644 index 000000000..bcf70bb48 --- /dev/null +++ b/src/Broadcasting/src/TopicRegistry.php @@ -0,0 +1,46 @@ + $callback) { + $this->register($topic, $callback); + } + } + + public function register(string $topic, callable $callback): void + { + $this->patterns[$this->compilePattern($topic)] = $callback; + } + + public function findCallback(string $topic, array &$matches): ?callable + { + foreach ($this->patterns as $pattern => $callback) { + if (preg_match($pattern, $topic, $matches)) { + return $callback; + } + } + + return null; + } + + private function compilePattern(string $topic): string + { + $replaces = []; + if (preg_match_all('/\{(\w+):?(.*?)?\}/', $topic, $matches)) { + $variables = array_combine($matches[1], $matches[2]); + foreach ($variables as $key => $_) { + $replaces['{' . $key . '}'] = '(?P<' . $key . '>[^\/\.]+)'; + } + } + + return '/^' . strtr($topic, $replaces + ['/' => '\\/', '[' => '(?:', ']' => ')?', '.' => '\.']) . '$/iu'; + } +} diff --git a/src/Broadcasting/src/TopicRegistryInterface.php b/src/Broadcasting/src/TopicRegistryInterface.php new file mode 100644 index 000000000..4752cd941 --- /dev/null +++ b/src/Broadcasting/src/TopicRegistryInterface.php @@ -0,0 +1,12 @@ +assertFalse($false->isSuccessful()); + + $true = new AuthorizationStatus(true, []); + $this->assertTrue($true->isSuccessful()); + } + + public function testGetsTopics(): void + { + $status = new AuthorizationStatus(false, $topics = ['topic1', 'topic2']); + $this->assertSame($topics, $status->getTopics()); + } + + public function testGetsAttributes(): void + { + $status = new AuthorizationStatus(false, ['topic1'], $attributes = ['foo' => 'bar']); + $this->assertSame($attributes, $status->getAttributes()); + } + + public function testGetsNullResponse(): void + { + $status = new AuthorizationStatus(false, ['topic1'], ['foo' => 'bar']); + $this->assertNull($status->getResponse()); + $this->assertFalse($status->hasResponse()); + } + + public function testGetsResponse(): void + { + $status = new AuthorizationStatus( + false, + ['topic1'], + ['foo' => 'bar'], + $response = m::mock(ResponseInterface::class) + ); + + $this->assertSame($response, $status->getResponse()); + $this->assertTrue($status->hasResponse()); + } +} diff --git a/src/Broadcasting/tests/BroadcastManagerTest.php b/src/Broadcasting/tests/BroadcastManagerTest.php new file mode 100644 index 000000000..bc4a97d60 --- /dev/null +++ b/src/Broadcasting/tests/BroadcastManagerTest.php @@ -0,0 +1,124 @@ + 'log', + 'aliases' => [ + 'firebase' => 'log', + ], + 'driverAliases' => [ + 'pusher' => 'pusher-driver-class', + ], + 'connections' => [ + 'log' => [ + 'driver' => 'pusher-driver-class', + 'foo' => 'bar', + ], + 'null' => [ + 'driver' => 'null-driver-class', + 'foo' => 'baz', + ], + 'inMemory' => [ + 'driver' => 'pusher', + 'bar' => 'baz', + ], + ], + ]); + + $this->factory = m::mock(FactoryInterface::class); + $this->manager = new BroadcastManager($this->factory, $config); + } + + public function testGetDefaultConnection(): void + { + $connection = m::mock(BroadcastInterface::class); + + $this->factory->shouldReceive('make')->once()->with('pusher-driver-class', [ + 'driver' => 'pusher-driver-class', + 'foo' => 'bar', + ])->andReturn($connection); + + $this->assertSame($connection, $this->manager->connection()); + } + + public function testGetConnectionByName(): void + { + $connection = m::mock(BroadcastInterface::class); + + $this->factory->shouldReceive('make')->once()->with('null-driver-class', [ + 'driver' => 'null-driver-class', + 'foo' => 'baz', + ])->andReturn($connection); + + $this->assertSame($connection, $this->manager->connection('null')); + } + + public function testGetConnectionWithDriverAlias(): void + { + $connection = m::mock(BroadcastInterface::class); + + $this->factory->shouldReceive('make')->once()->with('pusher-driver-class', [ + 'driver' => 'pusher-driver-class', + 'bar' => 'baz', + ])->andReturn($connection); + + $this->assertSame($connection, $this->manager->connection('inMemory')); + } + + public function testGetConnectionByAlias(): void + { + $connection = m::mock(BroadcastInterface::class); + + $this->factory->shouldReceive('make')->once()->with('pusher-driver-class', [ + 'driver' => 'pusher-driver-class', + 'foo' => 'bar', + ])->andReturn($connection); + + $this->assertSame($connection, $this->manager->connection('firebase')); + } + + public function testConnectionShouldBeCreatedOnlyOnce(): void + { + $connection1 = m::mock(BroadcastInterface::class); + $connection2 = m::mock(BroadcastInterface::class); + + $this->factory->shouldReceive('make')->once()->with('pusher-driver-class', [ + 'driver' => 'pusher-driver-class', + 'foo' => 'bar', + ])->andReturn($connection1); + + $this->assertSame($connection1, $this->manager->connection()); + $this->assertSame($connection1, $this->manager->connection()); + + $this->factory->shouldReceive('make')->once()->with('null-driver-class', [ + 'driver' => 'null-driver-class', + 'foo' => 'baz', + ])->andReturn($connection2); + + $this->assertSame($connection2, $this->manager->connection('null')); + $this->assertSame($connection2, $this->manager->connection('null')); + } +} diff --git a/src/Broadcasting/tests/Config/BroadcastConfigTest.php b/src/Broadcasting/tests/Config/BroadcastConfigTest.php new file mode 100644 index 000000000..a48d15160 --- /dev/null +++ b/src/Broadcasting/tests/Config/BroadcastConfigTest.php @@ -0,0 +1,122 @@ +config = new BroadcastConfig([ + 'authorize' => [ + 'path' => 'foo-path', + 'topics' => [ + 'bar-topic.{id}' => fn ($id) => $id, + 'foo-topic' => fn () => 'foo', + ], + ], + 'default' => 'firebase', + 'aliases' => [ + 'users-data' => 'firebase', + 'foo-data' => 'foo', + ], + + 'driverAliases' => [ + 'log' => 'log-driver', + ], + + 'connections' => [ + 'firebase' => [ + 'driver' => 'log', + ], + 'null' => [ + 'driver' => 'null-driver', + ], + 'memory' => [], + ], + ]); + } + + + public function testGetsDefaultConnection(): void + { + $this->assertSame( + 'firebase', + $this->config->getDefaultConnection() + ); + } + + public function testNotDefinedDefaultKeyShouldThrowAnException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectErrorMessage('Default broadcast connection is not defined.'); + + $config = new BroadcastConfig(); + + $config->getDefaultConnection(); + } + + public function testGetsConnectionConfigByName(): void + { + $this->assertSame( + [ + 'driver' => 'null-driver', + ], + $this->config->getConnectionConfig('null') + ); + } + + public function testGetsConnectionWithAliasDriverShouldBeReplacedWithRealDriver(): void + { + $this->assertSame( + [ + 'driver' => 'log-driver', + ], + $this->config->getConnectionConfig('firebase') + ); + } + + public function testNotDefinedConnectionShouldThrowAnException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectErrorMessage('Config for connection `foo` is not defined.'); + + $this->config->getConnectionConfig('foo'); + } + + public function testConnectionWithoutDefinedDriverShouldThrowAnException(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectErrorMessage('Driver for `memory` connection is not defined.'); + + $this->config->getConnectionConfig('memory'); + } + + public function testGetAuthorizationPath(): void + { + $this->assertSame('foo-path', $this->config->getAuthorizationPath()); + } + + public function testNotDefinedAuthorizationPathShouldReturnNull(): void + { + $config = new BroadcastConfig(); + $this->assertNull($config->getAuthorizationPath()); + } + + public function testGetsTopics(): void + { + $this->assertSame( + $this->config['authorize']['topics'], + $this->config->getTopics() + ); + } +} diff --git a/src/Broadcasting/tests/Driver/LogBroadcastTest.php b/src/Broadcasting/tests/Driver/LogBroadcastTest.php new file mode 100644 index 000000000..c196e1083 --- /dev/null +++ b/src/Broadcasting/tests/Driver/LogBroadcastTest.php @@ -0,0 +1,78 @@ +driver = new LogBroadcast( + $this->logger = m::mock(LoggerInterface::class) + ); + } + + public function testPublishMessageToTopicWithLoglevel(): void + { + $driver = new LogBroadcast( + $logger = m::mock(LoggerInterface::class), + LogLevel::DEBUG + ); + + $logger->shouldReceive('log')->once() + ->with(LogLevel::DEBUG, 'Broadcasting on channels [topic] with payload: message'); + $driver->publish('topic', 'message'); + } + + public function testPublishMessageToTopic(): void + { + $this->logger->shouldReceive('log')->once() + ->with(LogLevel::INFO, 'Broadcasting on channels [topic] with payload: message'); + $this->driver->publish('topic', 'message'); + } + + public function testPublishMessagesToTopic(): void + { + $this->logger->shouldReceive('log')->once() + ->with(LogLevel::INFO, 'Broadcasting on channels [topic] with payload: message1'); + $this->logger->shouldReceive('log')->once() + ->with(LogLevel::INFO, 'Broadcasting on channels [topic] with payload: message2'); + + $this->driver->publish('topic', ['message1', 'message2']); + } + + public function testPublishMessageToTopics(): void + { + $this->logger->shouldReceive('log')->once() + ->with(LogLevel::INFO, 'Broadcasting on channels [topic1, topic2] with payload: message'); + + $this->driver->publish(['topic1', 'topic2'], 'message'); + } + + public function testPublishMessagesToTopics(): void + { + $this->logger->shouldReceive('log')->once() + ->with(LogLevel::INFO, 'Broadcasting on channels [topic1, topic2] with payload: message1'); + $this->logger->shouldReceive('log')->once() + ->with(LogLevel::INFO, 'Broadcasting on channels [topic1, topic2] with payload: message2'); + + $this->driver->publish(['topic1', 'topic2'], ['message1', 'message2']); + } +} diff --git a/src/Broadcasting/tests/Driver/NullBroadcastTest.php b/src/Broadcasting/tests/Driver/NullBroadcastTest.php new file mode 100644 index 000000000..7651ed035 --- /dev/null +++ b/src/Broadcasting/tests/Driver/NullBroadcastTest.php @@ -0,0 +1,29 @@ +driver = new NullBroadcast(); + } + + public function testPublishMessageToTopic(): void + { + $this->driver->publish('topic', 'message'); + $this->assertTrue(true); + } +} diff --git a/src/Broadcasting/tests/Middleware/AuthorizationMiddlewareTest.php b/src/Broadcasting/tests/Middleware/AuthorizationMiddlewareTest.php new file mode 100644 index 000000000..76da57e3c --- /dev/null +++ b/src/Broadcasting/tests/Middleware/AuthorizationMiddlewareTest.php @@ -0,0 +1,130 @@ +shouldReceive('getUri')->once()->andReturn($uri = m::mock(UriInterface::class)); + $uri->shouldReceive('getPath')->once()->andReturn('/'); + + $handler->shouldReceive('handle')->once()->with($request); + + $middleware->process($request, $handler); + } + + public function testNotGuardedBroadcastShouldReturnOkResponse(): void + { + $middleware = new AuthorizationMiddleware( + $broadcast = m::mock(BroadcastInterface::class), + $responseFactory = m::mock(ResponseFactoryInterface::class), + '/auth', + ); + + $request = m::mock(ServerRequestInterface::class); + $handler = m::mock(RequestHandlerInterface::class); + + $request->shouldReceive('getUri')->once()->andReturn($uri = m::mock(UriInterface::class)); + $uri->shouldReceive('getPath')->once()->andReturn('/auth'); + + $responseFactory->shouldReceive('createResponse')->once()->with(200)->andReturn(m::mock(ResponseInterface::class)); + + $middleware->process($request, $handler); + } + + public function testGuardedBroadcastWithAuthorizedRequestShouldReturnOkResponse(): void + { + $middleware = new AuthorizationMiddleware( + $broadcast = m::mock(BroadcastInterface::class, GuardInterface::class), + $responseFactory = m::mock(ResponseFactoryInterface::class), + '/auth', + ); + + $request = m::mock(ServerRequestInterface::class); + $handler = m::mock(RequestHandlerInterface::class); + + $request->shouldReceive('getUri')->once()->andReturn($uri = m::mock(UriInterface::class)); + $uri->shouldReceive('getPath')->once()->andReturn('/auth'); + + $broadcast->shouldReceive('authorize')->once()->with($request) + ->andReturn(new AuthorizationStatus( + true, ['topic_name'], ['foo' => 'bar'] + )); + + $responseFactory->shouldReceive('createResponse')->once()->with(200)->andReturn(m::mock(ResponseInterface::class)); + + $middleware->process($request, $handler); + } + + public function testGuardedBroadcastWithNotAuthorizedRequestShouldReturnForbidResponse(): void + { + $middleware = new AuthorizationMiddleware( + $broadcast = m::mock(BroadcastInterface::class, GuardInterface::class), + $responseFactory = m::mock(ResponseFactoryInterface::class), + '/auth', + ); + + $request = m::mock(ServerRequestInterface::class); + $handler = m::mock(RequestHandlerInterface::class); + + $request->shouldReceive('getUri')->once()->andReturn($uri = m::mock(UriInterface::class)); + $uri->shouldReceive('getPath')->once()->andReturn('/auth'); + + $broadcast->shouldReceive('authorize')->once()->with($request) + ->andReturn(new AuthorizationStatus( + false, ['topic_name'], ['foo' => 'bar'] + )); + + $responseFactory->shouldReceive('createResponse')->once()->with(403)->andReturn(m::mock(ResponseInterface::class)); + + $middleware->process($request, $handler); + } + + public function testGuardedBroadcastWithCustomResponseShouldReturnIt(): void + { + $middleware = new AuthorizationMiddleware( + $broadcast = m::mock(BroadcastInterface::class, GuardInterface::class), + $responseFactory = m::mock(ResponseFactoryInterface::class), + '/auth', + ); + + $request = m::mock(ServerRequestInterface::class); + $handler = m::mock(RequestHandlerInterface::class); + + $request->shouldReceive('getUri')->once()->andReturn($uri = m::mock(UriInterface::class)); + $uri->shouldReceive('getPath')->once()->andReturn('/auth'); + + $broadcast->shouldReceive('authorize')->once()->with($request) + ->andReturn(new AuthorizationStatus( + false, ['topic_name'], ['foo' => 'bar'], $response = m::mock(ResponseInterface::class) + )); + + $this->assertSame($response, $middleware->process($request, $handler)); + } +} diff --git a/src/Broadcasting/tests/TopicRegistryTest.php b/src/Broadcasting/tests/TopicRegistryTest.php new file mode 100644 index 000000000..406337484 --- /dev/null +++ b/src/Broadcasting/tests/TopicRegistryTest.php @@ -0,0 +1,69 @@ +registry = new TopicRegistry([ + 'bar-topic.{id}' => fn($id) => $id, + 'foo-topic' => fn() => 'foo', + ]); + } + + public function testRegisterTopic(): void + { + $this->registry->register('baz-topic', fn() => 'baz'); + $this->registry->register('baz.{uuid}', fn(string $uuid) => $uuid); + + + $params = []; + $this->assertSame( + 'baz', + call_user_func($this->registry->findCallback('baz-topic', $params)) + ); + + + $params = []; + $this->assertSame( + 'hello', + call_user_func($this->registry->findCallback('baz.hello', $params), 'hello') + ); + $this->assertSame([0 => 'baz.hello', 'uuid' => 'hello', 1 => 'hello'], $params); + } + + public function testFindsTopicCallback(): void + { + $params = []; + $this->assertSame( + 'foo', + call_user_func($this->registry->findCallback('foo-topic', $params)) + ); + + $this->assertSame([0 => 'foo-topic'], $params); + + $params = []; + $this->assertSame( + 5, + call_user_func($this->registry->findCallback('bar-topic.5', $params), 5) + ); + $this->assertSame([0 => 'bar-topic.5', 'id' => '5', 1 => '5'], $params); + + + $params = []; + $this->assertNull( + $this->registry->findCallback('baz-topic', $params) + ); + $this->assertSame([], $params); + } +} diff --git a/src/Cache/composer.json b/src/Cache/composer.json index 46f002a1c..82c99cf77 100644 --- a/src/Cache/composer.json +++ b/src/Cache/composer.json @@ -16,9 +16,9 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", - "spiral/config": "^2.13", - "spiral/files": "^2.13", + "spiral/core": "^2.14", + "spiral/config": "^2.14", + "spiral/files": "^2.14", "psr/simple-cache": "2 - 3" }, "autoload": { @@ -27,7 +27,7 @@ } }, "require-dev": { - "spiral/boot": "^2.13", + "spiral/boot": "^2.14", "mockery/mockery": "^1.5", "phpunit/phpunit": "^9.5.5" }, @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Config/composer.json b/src/Config/composer.json index 32476f3e6..da068a8c3 100644 --- a/src/Config/composer.json +++ b/src/Config/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=8.1", "ext-json": "*", - "spiral/core": "^2.13" + "spiral/core": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5", @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Console/composer.json b/src/Console/composer.json index 3426deea3..97544955a 100644 --- a/src/Console/composer.json +++ b/src/Console/composer.json @@ -16,12 +16,12 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", + "spiral/core": "^2.14", "symfony/console": "^5.3.7|^6.0" }, "require-dev": { - "spiral/boot": "^2.13", - "spiral/tokenizer": "^2.13", + "spiral/boot": "^2.14", + "spiral/tokenizer": "^2.14", "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5" }, @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Cookies/composer.json b/src/Cookies/composer.json index faa524f99..84a3ded5d 100644 --- a/src/Cookies/composer.json +++ b/src/Cookies/composer.json @@ -16,14 +16,14 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", - "spiral/encrypter": "^2.13", + "spiral/core": "^2.14", + "spiral/encrypter": "^2.14", "psr/http-server-middleware": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5", - "spiral/http": "^2.13", + "spiral/http": "^2.14", "laminas/laminas-diactoros": "^2.8" }, "autoload": { @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Core/composer.json b/src/Core/composer.json index 8597c7084..358edb0a0 100644 --- a/src/Core/composer.json +++ b/src/Core/composer.json @@ -34,7 +34,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Csrf/composer.json b/src/Csrf/composer.json index a835b8c30..9ddd5fa9f 100644 --- a/src/Csrf/composer.json +++ b/src/Csrf/composer.json @@ -16,14 +16,14 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", - "spiral/cookies": "^2.13", + "spiral/core": "^2.14", + "spiral/cookies": "^2.14", "psr/http-server-middleware": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5", - "spiral/http": "^2.13", + "spiral/http": "^2.14", "laminas/laminas-diactoros": "^2.8" }, "autoload": { @@ -38,7 +38,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Debug/composer.json b/src/Debug/composer.json index e57a71310..2f1c82919 100644 --- a/src/Debug/composer.json +++ b/src/Debug/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=8.1", - "spiral/logger": "^2.13" + "spiral/logger": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5" @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Distribution/composer.json b/src/Distribution/composer.json index d05b5cb07..54a2084bb 100644 --- a/src/Distribution/composer.json +++ b/src/Distribution/composer.json @@ -41,7 +41,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "scripts": { diff --git a/src/Dumper/composer.json b/src/Dumper/composer.json index 48fe7842f..9106c9bf6 100644 --- a/src/Dumper/composer.json +++ b/src/Dumper/composer.json @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Dumper/src/Dumper.php b/src/Dumper/src/Dumper.php index 45ec57858..ba51d86f3 100644 --- a/src/Dumper/src/Dumper.php +++ b/src/Dumper/src/Dumper.php @@ -14,6 +14,8 @@ /** * Renderer exports the content of the given variable, array or object into human friendly form. + * + * @deprecated since v2.13. Will be removed in v3.0 */ class Dumper implements LoggerAwareInterface { diff --git a/src/Dumper/src/Exception/DumperException.php b/src/Dumper/src/Exception/DumperException.php index 1e2ef9fe0..9ebe7a414 100644 --- a/src/Dumper/src/Exception/DumperException.php +++ b/src/Dumper/src/Exception/DumperException.php @@ -4,6 +4,9 @@ namespace Spiral\Debug\Exception; +/** + * @deprecated since v2.13. Will be removed in v3.0 + */ class DumperException extends \LogicException { } diff --git a/src/Dumper/src/Renderer/AbstractRenderer.php b/src/Dumper/src/Renderer/AbstractRenderer.php index 1ca38b06f..9a4313729 100644 --- a/src/Dumper/src/Renderer/AbstractRenderer.php +++ b/src/Dumper/src/Renderer/AbstractRenderer.php @@ -6,6 +6,9 @@ use Spiral\Debug\RendererInterface; +/** + * @deprecated since v2.13. Will be removed in v3.0 + */ abstract class AbstractRenderer implements RendererInterface { /** diff --git a/src/Dumper/src/Renderer/ConsoleRenderer.php b/src/Dumper/src/Renderer/ConsoleRenderer.php index 78b878cbf..5769e2179 100644 --- a/src/Dumper/src/Renderer/ConsoleRenderer.php +++ b/src/Dumper/src/Renderer/ConsoleRenderer.php @@ -8,6 +8,8 @@ /** * Colorful styling for CLI dumps. + * + * @deprecated since v2.13. Will be removed in v3.0 */ final class ConsoleRenderer extends AbstractRenderer { diff --git a/src/Dumper/src/Renderer/HtmlRenderer.php b/src/Dumper/src/Renderer/HtmlRenderer.php index 8e9ee167d..ddc8c3853 100644 --- a/src/Dumper/src/Renderer/HtmlRenderer.php +++ b/src/Dumper/src/Renderer/HtmlRenderer.php @@ -8,6 +8,8 @@ /** * HTML renderer with switchable color schemas. + * + * @deprecated since v2.13. Will be removed in v3.0 */ final class HtmlRenderer implements RendererInterface { diff --git a/src/Dumper/src/Renderer/PlainRenderer.php b/src/Dumper/src/Renderer/PlainRenderer.php index 578fe137f..2d4d2fbd2 100644 --- a/src/Dumper/src/Renderer/PlainRenderer.php +++ b/src/Dumper/src/Renderer/PlainRenderer.php @@ -6,6 +6,8 @@ /** * No styles. + * + * @deprecated since v2.13. Will be removed in v3.0 */ final class PlainRenderer extends AbstractRenderer { diff --git a/src/Dumper/src/RendererInterface.php b/src/Dumper/src/RendererInterface.php index be2d55155..b54baa8ae 100644 --- a/src/Dumper/src/RendererInterface.php +++ b/src/Dumper/src/RendererInterface.php @@ -6,6 +6,8 @@ /** * Applies set of styles to value dump. + * + * @deprecated since v2.13. Will be removed in v3.0 */ interface RendererInterface { diff --git a/src/Dumper/src/System.php b/src/Dumper/src/System.php index 7f815b811..fe0b4fe25 100644 --- a/src/Dumper/src/System.php +++ b/src/Dumper/src/System.php @@ -6,6 +6,8 @@ /** * Describes the env PHP script is running within. + * + * @deprecated since v2.13. Will be removed in v3.0 */ final class System { diff --git a/src/Encrypter/composer.json b/src/Encrypter/composer.json index 47bf232e8..5b2b10fea 100644 --- a/src/Encrypter/composer.json +++ b/src/Encrypter/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=8.1", "ext-json": "*", - "spiral/core": "^2.13", + "spiral/core": "^2.14", "defuse/php-encryption": "^2.2" }, "require-dev": { @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Exceptions/composer.json b/src/Exceptions/composer.json index 5fc15b0f0..10f4e4851 100644 --- a/src/Exceptions/composer.json +++ b/src/Exceptions/composer.json @@ -18,8 +18,8 @@ "php": ">=8.1", "ext-json": "*", "ext-mbstring": "*", - "spiral/dumper": "^2.13", - "spiral/debug": "^2.13" + "spiral/dumper": "^2.14", + "spiral/debug": "^2.14" }, "require-dev": { "mockery/mockery": "^1.5", @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Files/composer.json b/src/Files/composer.json index 55caabf32..b881254c7 100644 --- a/src/Files/composer.json +++ b/src/Files/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Filters/composer.json b/src/Filters/composer.json index b151cd1a0..1b9d33421 100644 --- a/src/Filters/composer.json +++ b/src/Filters/composer.json @@ -16,10 +16,10 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", - "spiral/models": "^2.13", - "spiral/translator": "^2.13", - "spiral/validation": "^2.13" + "spiral/core": "^2.14", + "spiral/models": "^2.14", + "spiral/translator": "^2.14", + "spiral/validation": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5", @@ -37,7 +37,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Framework/Bootloader/Http/HttpBootloader.php b/src/Framework/Bootloader/Http/HttpBootloader.php index 6cc956f98..4377ae04b 100644 --- a/src/Framework/Bootloader/Http/HttpBootloader.php +++ b/src/Framework/Bootloader/Http/HttpBootloader.php @@ -40,6 +40,7 @@ public function init(): void 'Content-Type' => 'text/html; charset=UTF-8', ], 'middleware' => [], + 'chunkSize' => null, ] ); } diff --git a/src/Hmvc/composer.json b/src/Hmvc/composer.json index e8ab3fc5c..0d9b1e335 100644 --- a/src/Hmvc/composer.json +++ b/src/Hmvc/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13" + "spiral/core": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5" @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Http/composer.json b/src/Http/composer.json index 418603a00..ede2293ab 100644 --- a/src/Http/composer.json +++ b/src/Http/composer.json @@ -18,9 +18,9 @@ "php": ">=8.1", "ext-json": "*", "ext-mbstring": "*", - "spiral/core": "^2.13", - "spiral/files": "^2.13", - "spiral/streams": "^2.13", + "spiral/core": "^2.14", + "spiral/files": "^2.14", + "spiral/streams": "^2.14", "psr/http-message": "^1.0", "psr/http-factory": "^1.0", "psr/http-server-middleware": "^1.0", @@ -44,7 +44,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Http/src/Config/HttpConfig.php b/src/Http/src/Config/HttpConfig.php index 170da03e8..5165bd44a 100644 --- a/src/Http/src/Config/HttpConfig.php +++ b/src/Http/src/Config/HttpConfig.php @@ -20,6 +20,7 @@ final class HttpConfig extends InjectableConfig 'Content-Type' => 'text/html; charset=UTF-8', ], 'middleware' => [], + 'chunkSize' => null, ]; public function getBasePath(): string @@ -44,4 +45,16 @@ public function getMiddleware(): array { return $this->config['middleware'] ?? $this->config['middlewares']; } + + /** + * If chunkSize isn't provided - using default values + */ + public function getChunkSize(): ?int + { + if (\is_null($this->config['chunkSize']) || (int) $this->config['chunkSize'] < 0) { + return null; + } + + return (int) $this->config['chunkSize']; + } } diff --git a/src/Logger/composer.json b/src/Logger/composer.json index b0159bd14..ffeba7b46 100644 --- a/src/Logger/composer.json +++ b/src/Logger/composer.json @@ -17,7 +17,7 @@ "require": { "php": ">=8.1", "psr/log": "1 - 3", - "spiral/core": "^2.13" + "spiral/core": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5", @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Mailer/composer.json b/src/Mailer/composer.json index 767e46dcb..09692d27d 100644 --- a/src/Mailer/composer.json +++ b/src/Mailer/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Models/composer.json b/src/Models/composer.json index 8095df668..1092e4875 100644 --- a/src/Models/composer.json +++ b/src/Models/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Pagination/composer.json b/src/Pagination/composer.json index e45419ffe..04a65b0a7 100644 --- a/src/Pagination/composer.json +++ b/src/Pagination/composer.json @@ -33,7 +33,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Prototype/composer.json b/src/Prototype/composer.json index 84730523b..c26d67297 100644 --- a/src/Prototype/composer.json +++ b/src/Prototype/composer.json @@ -19,8 +19,8 @@ "ext-json": "*", "nikic/php-parser": "^4.1", "doctrine/inflector": "^1.4|^2.0", - "spiral/console": "^2.13", - "spiral/attributes": "^2.13" + "spiral/console": "^2.14", + "spiral/annotations": "^2.14" }, "autoload": { "psr-4": { @@ -31,7 +31,7 @@ "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5", "cycle/orm": "^1.8.1", - "spiral/debug": "^2.13" + "spiral/debug": "^2.14" }, "autoload-dev": { "psr-4": { @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Queue/composer.json b/src/Queue/composer.json index 94bc2e620..dda5c9539 100644 --- a/src/Queue/composer.json +++ b/src/Queue/composer.json @@ -16,7 +16,8 @@ "require": { "php": ">=8.1", "ext-json": "*", - "spiral/snapshots": "^2.13", + "spiral/snapshots": "^2.14", + "spiral/attributes": "^2.14", "doctrine/inflector": "^1.4|^2.0", "opis/closure": "^3.6", "ramsey/uuid": "^4.2.3" @@ -32,13 +33,14 @@ } }, "require-dev": { + "doctrine/annotations": "^1.12", "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5", - "spiral/boot": "^2.13" + "spiral/boot": "^2.14" }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Queue/src/Attribute/Queueable.php b/src/Queue/src/Attribute/Queueable.php new file mode 100644 index 000000000..7dd7e3c18 --- /dev/null +++ b/src/Queue/src/Attribute/Queueable.php @@ -0,0 +1,29 @@ +queue = $queue; + } +} diff --git a/src/Queue/src/QueueableDetector.php b/src/Queue/src/QueueableDetector.php new file mode 100644 index 000000000..bbc1f587c --- /dev/null +++ b/src/Queue/src/QueueableDetector.php @@ -0,0 +1,51 @@ +reader = $reader; + } + + /** + * @psalm-param class-string|object $object + */ + public function isQueueable($object): bool + { + $reflection = new \ReflectionClass($object); + + if ($reflection->implementsInterface(QueueableInterface::class)) { + return true; + } + + return $this->reader->firstClassMetadata($reflection, Queueable::class) !== null; + } + + /** + * @psalm-param class-string|object $object + */ + public function getQueue($object): ?string + { + $reflection = new \ReflectionClass($object); + + $attribute = $this->reader->firstClassMetadata($reflection, Queueable::class); + if ($attribute !== null) { + return $attribute->queue; + } + + if (\is_object($object) && $reflection->hasMethod('getQueue')) { + return $reflection->getMethod('getQueue')->invoke($object); + } + + return null; + } +} diff --git a/src/Queue/tests/Attribute/Stub/NotQueueable.php b/src/Queue/tests/Attribute/Stub/NotQueueable.php new file mode 100644 index 000000000..d7a8e019d --- /dev/null +++ b/src/Queue/tests/Attribute/Stub/NotQueueable.php @@ -0,0 +1,9 @@ +create()); + + $this->assertSame($queueable, $detector->isQueueable($object)); + $this->assertSame($queue, $detector->getQueue($object)); + } + + public function queueableProvider(): \Traversable + { + yield [Queueable::class, true, null]; + yield [QueueableWithQueue::class, true, 'test']; + yield [QueueableWithInterface::class, true, null]; + yield [new QueueableWithInterfaceAndQueue(), true, 'test']; + yield [new QueueableWithInterfaceAndStaticQueue(), true, 'test']; + yield [NotQueueable::class, false, null]; + yield [new NotQueueable(), false, null]; + yield [NotQueueableInterface::class, false, null]; + } +} diff --git a/src/Reactor/composer.json b/src/Reactor/composer.json index 48fecc7f3..c82c74e19 100644 --- a/src/Reactor/composer.json +++ b/src/Reactor/composer.json @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Router/composer.json b/src/Router/composer.json index c0f780b53..23a87c3f7 100644 --- a/src/Router/composer.json +++ b/src/Router/composer.json @@ -17,9 +17,9 @@ "require": { "php": ">=8.1", "ext-json": "*", - "spiral/core": "^2.13", - "spiral/hmvc": "^2.13", - "spiral/http": "^2.13", + "spiral/core": "^2.14", + "spiral/hmvc": "^2.14", + "spiral/http": "^2.14", "cocur/slugify": "^3.2", "doctrine/inflector": "^1.4|^2.0" }, @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Scaffolder/composer.json b/src/Scaffolder/composer.json index 854574893..3d4aded37 100644 --- a/src/Scaffolder/composer.json +++ b/src/Scaffolder/composer.json @@ -21,7 +21,7 @@ ], "require": { "php": ">=8.1", - "spiral/reactor": "^2.13", + "spiral/reactor": "^2.14", "cocur/slugify": "^3.2", "doctrine/inflector": "^1.4|^2.0" }, @@ -29,14 +29,14 @@ "phpunit/phpunit": "^9.5.5", "cycle/orm": "^1.8.1", "cycle/annotated": "^2.0.6", - "spiral/boot": "^2.13", - "spiral/console": "^2.13", - "spiral/core": "^2.13", - "spiral/filters": "^2.13", - "spiral/http": "^2.13", + "spiral/boot": "^2.14", + "spiral/console": "^2.14", + "spiral/core": "^2.14", + "spiral/filters": "^2.14", + "spiral/http": "^2.14", "spiral/migrations": "^2.3", - "spiral/prototype": "^2.13", - "spiral/queue": "^2.13" + "spiral/prototype": "^2.14", + "spiral/queue": "^2.14" }, "autoload": { "files": [ @@ -53,7 +53,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Security/composer.json b/src/Security/composer.json index 780ef5c3f..903ba244e 100644 --- a/src/Security/composer.json +++ b/src/Security/composer.json @@ -16,7 +16,7 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13" + "spiral/core": "^2.14" }, "autoload": { "psr-4": { @@ -24,7 +24,7 @@ } }, "require-dev": { - "spiral/console": "^2.13", + "spiral/console": "^2.14", "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5" }, @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/SendIt/composer.json b/src/SendIt/composer.json index 9629e1a54..92cc44d42 100644 --- a/src/SendIt/composer.json +++ b/src/SendIt/composer.json @@ -17,19 +17,19 @@ "require": { "php": ">=8.1", "ext-json": "*", - "spiral/core": "^2.13", + "spiral/core": "^2.14", "spiral/jobs": "^2.2", - "spiral/queue": "^2.13", - "spiral/logger": "^2.13", - "spiral/mailer": "^2.13", - "spiral/views": "^2.13", + "spiral/queue": "^2.14", + "spiral/logger": "^2.14", + "spiral/mailer": "^2.14", + "spiral/views": "^2.14", "symfony/mailer": "^5.1|^6.0" }, "require-dev": { "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5", - "spiral/boot": "^2.13", - "spiral/stempler-bridge": "^2.13" + "spiral/boot": "^2.14", + "spiral/stempler-bridge": "^2.14" }, "autoload": { "psr-4": { @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Session/composer.json b/src/Session/composer.json index ea7c3ab9c..e6226e69e 100644 --- a/src/Session/composer.json +++ b/src/Session/composer.json @@ -16,8 +16,8 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", - "spiral/files": "^2.13" + "spiral/core": "^2.14", + "spiral/files": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5" @@ -34,7 +34,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Snapshots/composer.json b/src/Snapshots/composer.json index 34f2501eb..6eb15e47f 100644 --- a/src/Snapshots/composer.json +++ b/src/Snapshots/composer.json @@ -32,7 +32,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Stempler/composer.json b/src/Stempler/composer.json index d5e09a07d..3d1057f76 100644 --- a/src/Stempler/composer.json +++ b/src/Stempler/composer.json @@ -22,7 +22,7 @@ "require-dev": { "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5", - "spiral/dumper": "^2.13" + "spiral/dumper": "^2.14" }, "autoload": { "files": [ @@ -39,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Storage/composer.json b/src/Storage/composer.json index 65d26d6f3..63cdfcf19 100644 --- a/src/Storage/composer.json +++ b/src/Storage/composer.json @@ -20,7 +20,7 @@ ], "require": { "php": ">=8.1", - "spiral/distribution": "^2.13", + "spiral/distribution": "^2.14", "symfony/polyfill-php80": "^1.22", "league/flysystem": "^2.3.1" }, @@ -32,7 +32,7 @@ "require-dev": { "phpunit/phpunit": "^9.5.5", "symfony/var-dumper": "^5.2|^6.0", - "vimeo/psalm": "^4.22", + "vimeo/psalm": "^4.21", "jetbrains/phpstorm-attributes": "^1.0" }, "autoload-dev": { @@ -52,7 +52,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Streams/composer.json b/src/Streams/composer.json index 5903a0140..2dd425703 100644 --- a/src/Streams/composer.json +++ b/src/Streams/composer.json @@ -21,7 +21,7 @@ "require-dev": { "phpunit/phpunit": "^9.5.5", "mockery/mockery": "^1.5", - "spiral/files": "^2.13", + "spiral/files": "^2.14", "laminas/laminas-diactoros": "^2.8" }, "autoload": { @@ -36,7 +36,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Tokenizer/composer.json b/src/Tokenizer/composer.json index 0661d2427..93d8200d0 100644 --- a/src/Tokenizer/composer.json +++ b/src/Tokenizer/composer.json @@ -16,8 +16,8 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", - "spiral/logger": "^2.13", + "spiral/core": "^2.14", + "spiral/logger": "^2.14", "symfony/finder": "^5.3.7|^6.0" }, "require-dev": { @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Translator/composer.json b/src/Translator/composer.json index 441c5a646..05762d873 100644 --- a/src/Translator/composer.json +++ b/src/Translator/composer.json @@ -16,9 +16,9 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", - "spiral/logger": "^2.13", - "spiral/tokenizer": "^2.13", + "spiral/core": "^2.14", + "spiral/logger": "^2.14", + "spiral/tokenizer": "^2.14", "symfony/translation": "^5.1|^6.0" }, "require-dev": { @@ -40,7 +40,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Validation/composer.json b/src/Validation/composer.json index 678ae71c1..e33e4889b 100644 --- a/src/Validation/composer.json +++ b/src/Validation/composer.json @@ -17,10 +17,10 @@ "require": { "php": ">=8.1", "ext-json": "*", - "spiral/core": "^2.13", - "spiral/files": "^2.13", - "spiral/streams": "^2.13", - "spiral/translator": "^2.13" + "spiral/core": "^2.14", + "spiral/files": "^2.14", + "spiral/streams": "^2.14", + "spiral/translator": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5", @@ -39,7 +39,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/src/Views/composer.json b/src/Views/composer.json index a69955831..9b4e6dc93 100644 --- a/src/Views/composer.json +++ b/src/Views/composer.json @@ -16,8 +16,8 @@ ], "require": { "php": ">=8.1", - "spiral/core": "^2.13", - "spiral/files": "^2.13" + "spiral/core": "^2.14", + "spiral/files": "^2.14" }, "require-dev": { "phpunit/phpunit": "^9.5.5", @@ -35,7 +35,7 @@ }, "extra": { "branch-alias": { - "dev-master": "2.13.x-dev" + "dev-master": "2.14.x-dev" } }, "config": { diff --git a/tests/app/config/broadcasting.php b/tests/app/config/broadcasting.php new file mode 100644 index 000000000..3df44bbd4 --- /dev/null +++ b/tests/app/config/broadcasting.php @@ -0,0 +1,28 @@ + 'log', + 'aliases' => [ + 'firebase' => 'null', + ], + 'connections' => [ + 'null' => [ + 'driver' => NullBroadcast::class, + ], + 'log' => [ + 'driver' => LogBroadcast::class, + 'level' => \Psr\Log\LogLevel::DEBUG, + ], + 'nullable' => [ + 'type' => 'null', + ], + ], + 'driverAliases' => [ + 'null' => NullBroadcast::class, + ], +]; diff --git a/tests/app/src/TestApp.php b/tests/app/src/TestApp.php index ec8f1951f..0b77ebba5 100644 --- a/tests/app/src/TestApp.php +++ b/tests/app/src/TestApp.php @@ -53,6 +53,10 @@ class TestApp extends Kernel // Cache \Spiral\Cache\Bootloader\CacheBootloader::class, + // Broadcasting + \Spiral\Broadcasting\Bootloader\BroadcastingBootloader::class, + \Spiral\Broadcasting\Bootloader\WebsocketsBootloader::class, + // Auth Bootloader\Auth\HttpAuthBootloader::class,