From 8d2a243c51566bbc7a0cfcf38d2c3baf1aba7d2f Mon Sep 17 00:00:00 2001 From: Deeka Wong Date: Mon, 19 Jun 2023 14:56:46 +0800 Subject: [PATCH] Adds `hyperf/closure-command` component (#5855) --- CHANGELOG-3.1.md | 1 + src/closure-command/.gitattributes | 2 + .../.github/workflows/close-pull-request.yml | 13 ++++ .../.github/workflows/release.yml | 25 +++++++ src/closure-command/LICENSE | 21 ++++++ src/closure-command/composer.json | 45 ++++++++++++ src/closure-command/publish/console.php | 16 +++++ src/closure-command/src/ClosureCommand.php | 44 ++++++++++++ src/closure-command/src/ConfigProvider.php | 34 +++++++++ src/closure-command/src/Console.php | 52 ++++++++++++++ .../src/Listener/RegisterCommandListener.php | 64 +++++++++++++++++ src/closure-command/src/ParameterParser.php | 71 +++++++++++++++++++ src/closure-command/tests/.gitkeep | 0 13 files changed, 388 insertions(+) create mode 100644 src/closure-command/.gitattributes create mode 100644 src/closure-command/.github/workflows/close-pull-request.yml create mode 100644 src/closure-command/.github/workflows/release.yml create mode 100644 src/closure-command/LICENSE create mode 100644 src/closure-command/composer.json create mode 100644 src/closure-command/publish/console.php create mode 100644 src/closure-command/src/ClosureCommand.php create mode 100644 src/closure-command/src/ConfigProvider.php create mode 100644 src/closure-command/src/Console.php create mode 100644 src/closure-command/src/Listener/RegisterCommandListener.php create mode 100644 src/closure-command/src/ParameterParser.php create mode 100644 src/closure-command/tests/.gitkeep diff --git a/CHANGELOG-3.1.md b/CHANGELOG-3.1.md index daba67bf1..907eafc69 100644 --- a/CHANGELOG-3.1.md +++ b/CHANGELOG-3.1.md @@ -23,6 +23,7 @@ - [x] Added `hyperf/polyfill-coroutine` component. - [#5815](https://github.com/hyperf/hyperf/pull/5815) Added alias as `mysql` for `pdo` in `hyperf/db`. - [#5849](https://github.com/hyperf/hyperf/pull/5849) Support for insert update and select using enums. +- [#5855](https://github.com/hyperf/hyperf/pull/5855) Added `hyperf/closure-command` component. ## Optimized diff --git a/src/closure-command/.gitattributes b/src/closure-command/.gitattributes new file mode 100644 index 000000000..27b765f55 --- /dev/null +++ b/src/closure-command/.gitattributes @@ -0,0 +1,2 @@ +/tests export-ignore +/.github export-ignore diff --git a/src/closure-command/.github/workflows/close-pull-request.yml b/src/closure-command/.github/workflows/close-pull-request.yml new file mode 100644 index 000000000..591822c0f --- /dev/null +++ b/src/closure-command/.github/workflows/close-pull-request.yml @@ -0,0 +1,13 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: "Hi, this is a READ-ONLY repository, please submit your PR on the https://github.com/hyperf/hyperf repository.

This Pull Request will close automatically.

Thanks! " diff --git a/src/closure-command/.github/workflows/release.yml b/src/closure-command/.github/workflows/release.yml new file mode 100644 index 000000000..0f7d23faa --- /dev/null +++ b/src/closure-command/.github/workflows/release.yml @@ -0,0 +1,25 @@ +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +name: Release + +jobs: + release: + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false diff --git a/src/closure-command/LICENSE b/src/closure-command/LICENSE new file mode 100644 index 000000000..c35d3f5a8 --- /dev/null +++ b/src/closure-command/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Hyperf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/closure-command/composer.json b/src/closure-command/composer.json new file mode 100644 index 000000000..13be3c4a7 --- /dev/null +++ b/src/closure-command/composer.json @@ -0,0 +1,45 @@ +{ + "name": "hyperf/closure-command", + "type": "library", + "license": "MIT", + "keywords": [ + "php", + "swoole", + "closure-command" + ], + "description": "The Closure Command component for hyperf", + "autoload": { + "psr-4": { + "Hyperf\\ClosureCommand\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "HyperfTest\\ClosureCommand\\": "tests/" + } + }, + "require": { + "php": ">=8.1", + "hyperf/collection": "~3.1.0", + "hyperf/command": "~3.1.0", + "hyperf/di": "~3.1.0", + "hyperf/event": "~3.1.0", + "hyperf/stringable": "~3.1.0", + "hyperf/support": "~3.1.0", + "hyperf/tappable": "~3.1.0" + }, + "suggest": { + "hyperf/di": "Required to use annotations." + }, + "config": { + "sort-packages": true + }, + "extra": { + "hyperf": { + "config": "Hyperf\\ClosureCommand\\ConfigProvider" + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + } +} diff --git a/src/closure-command/publish/console.php b/src/closure-command/publish/console.php new file mode 100644 index 000000000..102a18d2b --- /dev/null +++ b/src/closure-command/publish/console.php @@ -0,0 +1,16 @@ +comment('Hello, Hyperf!'); +})->describe('This a closure command demo.'); diff --git a/src/closure-command/src/ClosureCommand.php b/src/closure-command/src/ClosureCommand.php new file mode 100644 index 000000000..0d9c840a3 --- /dev/null +++ b/src/closure-command/src/ClosureCommand.php @@ -0,0 +1,44 @@ +signature = $signature; + $this->parameterParser = $container->get(ParameterParser::class); + + parent::__construct(); + } + + public function handle() + { + $inputs = array_merge($this->input->getArguments(), $this->input->getOptions()); + $parameters = $this->parameterParser->parseClosureParameters($this->closure, $inputs); + + return $this->closure->call($this, ...$parameters); + } + + public function describe(string $description): self + { + $this->setDescription($description); + + return $this; + } +} diff --git a/src/closure-command/src/ConfigProvider.php b/src/closure-command/src/ConfigProvider.php new file mode 100644 index 000000000..aa40da922 --- /dev/null +++ b/src/closure-command/src/ConfigProvider.php @@ -0,0 +1,34 @@ + [ + RegisterCommandListener::class, + ], + 'publish' => [ + [ + 'id' => 'config', + 'description' => 'The console route file of closure-command.', + 'source' => __DIR__ . '/../publish/console.php', + 'destination' => Console::ROUTE, + ], + ], + ]; + } +} diff --git a/src/closure-command/src/Console.php b/src/closure-command/src/Console.php new file mode 100644 index 000000000..aa63e3fa3 --- /dev/null +++ b/src/closure-command/src/Console.php @@ -0,0 +1,52 @@ + $signature, + 'closure' => $command, + ]), static function ($handler) { + $handlerId = spl_object_hash($handler); + self::$commands[$handlerId] = $handler; + }); + } + + /** + * @return ClosureCommand[] + */ + public static function getCommands(): array + { + return self::$commands; + } +} diff --git a/src/closure-command/src/Listener/RegisterCommandListener.php b/src/closure-command/src/Listener/RegisterCommandListener.php new file mode 100644 index 000000000..ec8d2af62 --- /dev/null +++ b/src/closure-command/src/Listener/RegisterCommandListener.php @@ -0,0 +1,64 @@ +registerClosureCommands(); + } + + private function registerClosureCommands(): void + { + $route = Console::ROUTE; + + if (! file_exists($route)) { + return; + } + + require_once $route; + + foreach (Console::getCommands() as $handlerId => $command) { + $this->container->set($handlerId, $command); + $this->appendConfig('commands', $handlerId); + } + } + + private function appendConfig(string $key, $configValues): void + { + $configs = $this->config->get($key, []); + array_push($configs, $configValues); + $this->config->set($key, $configs); + } +} diff --git a/src/closure-command/src/ParameterParser.php b/src/closure-command/src/ParameterParser.php new file mode 100644 index 000000000..717a53ad0 --- /dev/null +++ b/src/closure-command/src/ParameterParser.php @@ -0,0 +1,71 @@ +normalizer = $this->container->get(NormalizerInterface::class); + + if ($this->container->has(ClosureDefinitionCollectorInterface::class)) { + $this->closureDefinitionCollector = $this->container->get(ClosureDefinitionCollectorInterface::class); + } + } + + public function parseClosureParameters(Closure $closure, array $arguments): array + { + if (! $this->closureDefinitionCollector) { + return []; + } + + $definitions = $this->closureDefinitionCollector->getParameters($closure); + + return $this->getInjections($definitions, 'Closure', $arguments); + } + + private function getInjections(array $definitions, string $callableName, array $arguments): array + { + $injections = []; + + foreach ($definitions as $pos => $definition) { + $value = $arguments[$pos] ?? $arguments[$definition->getMeta('name')] ?? $arguments[Str::snake($definition->getMeta('name'), '-')] ?? null; + if ($value === null) { + if ($definition->getMeta('defaultValueAvailable')) { + $injections[] = $definition->getMeta('defaultValue'); + } elseif ($this->container->has($definition->getName())) { + $injections[] = $this->container->get($definition->getName()); + } elseif ($definition->allowsNull()) { + $injections[] = null; + } else { + throw new InvalidArgumentException("Parameter '{$definition->getMeta('name')}' " + . "of {$callableName} should not be null"); + } + } else { + $injections[] = $this->normalizer->denormalize($value, $definition->getName()); + } + } + + return $injections; + } +} diff --git a/src/closure-command/tests/.gitkeep b/src/closure-command/tests/.gitkeep new file mode 100644 index 000000000..e69de29bb