From e7d19dcad146a28d3399e5ac42e92a63d6031bb9 Mon Sep 17 00:00:00 2001 From: Akolzin Dmitry Date: Tue, 30 Mar 2021 14:08:11 +0300 Subject: [PATCH] tests and doc --- Messenger/{Message.php => CommandMessage.php} | 2 +- Messenger/MessageHandler.php | 4 +- .../MessageHandler/InNewProcessRunner.php | 8 +- Messenger/MessageHandler/JobRunner.php | 6 +- .../MessageHandler/SimpleConsoleRunner.php | 11 +- Resources/doc/Messenger.md | 60 +++++++ Resources/doc/Requests.md | 71 +++++++++ Resources/doc/Security.md | 70 ++++++++ Resources/doc/index.md | 149 +----------------- Tests/Fixtures/App/Kernel.php | 33 ++++ Tests/Fixtures/App/TestCommand.php | 36 +++++ Tests/Fixtures/App/TestCommandMessage.php | 15 ++ Tests/Fixtures/bin/console | 9 ++ .../MessageHandler/InNewProcessRunnerTest.php | 27 ++++ .../SimpleConsoleRunnerTest.php | 27 ++++ composer.json | 3 +- phpunit.xml.dist | 3 + 17 files changed, 375 insertions(+), 159 deletions(-) rename Messenger/{Message.php => CommandMessage.php} (98%) create mode 100644 Resources/doc/Messenger.md create mode 100644 Resources/doc/Requests.md create mode 100644 Resources/doc/Security.md create mode 100644 Tests/Fixtures/App/Kernel.php create mode 100644 Tests/Fixtures/App/TestCommand.php create mode 100644 Tests/Fixtures/App/TestCommandMessage.php create mode 100644 Tests/Fixtures/bin/console create mode 100644 Tests/Messenger/MessageHandler/InNewProcessRunnerTest.php create mode 100644 Tests/Messenger/MessageHandler/SimpleConsoleRunnerTest.php diff --git a/Messenger/Message.php b/Messenger/CommandMessage.php similarity index 98% rename from Messenger/Message.php rename to Messenger/CommandMessage.php index c4fdb23..9ea7cde 100644 --- a/Messenger/Message.php +++ b/Messenger/CommandMessage.php @@ -7,7 +7,7 @@ namespace RetailCrm\ServiceBundle\Messenger; * * @package RetailCrm\ServiceBundle\Messenger */ -abstract class Message +abstract class CommandMessage { /** @var string */ protected $commandName; diff --git a/Messenger/MessageHandler.php b/Messenger/MessageHandler.php index f8cc6ce..6f38fa3 100644 --- a/Messenger/MessageHandler.php +++ b/Messenger/MessageHandler.php @@ -29,11 +29,11 @@ class MessageHandler implements MessageHandlerInterface } /** - * @param Message $message + * @param CommandMessage $message * * @throws Exception */ - public function __invoke(Message $message): void + public function __invoke(CommandMessage $message): void { $this->runner->run($message); } diff --git a/Messenger/MessageHandler/InNewProcessRunner.php b/Messenger/MessageHandler/InNewProcessRunner.php index 4951610..da0abff 100644 --- a/Messenger/MessageHandler/InNewProcessRunner.php +++ b/Messenger/MessageHandler/InNewProcessRunner.php @@ -3,7 +3,7 @@ namespace RetailCrm\ServiceBundle\Messenger\MessageHandler; use Psr\Log\LoggerInterface; -use RetailCrm\ServiceBundle\Messenger\Message; +use RetailCrm\ServiceBundle\Messenger\CommandMessage; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Process\Exception\ProcessTimedOutException; use Symfony\Component\Process\PhpExecutableFinder; @@ -57,7 +57,7 @@ class InNewProcessRunner implements JobRunner /** * {@inheritdoc} */ - public function run(Message $message): void + public function run(CommandMessage $message): void { $phpBinaryPath = (new PhpExecutableFinder)->find(); $consoleCommand = [ @@ -93,11 +93,11 @@ class InNewProcessRunner implements JobRunner } /** - * @param Message $message + * @param CommandMessage $message * * @return array */ - private function getOptions(Message $message): array + private function getOptions(CommandMessage $message): array { $options = []; foreach ($message->getFormattedOptions() as $option => $value) { diff --git a/Messenger/MessageHandler/JobRunner.php b/Messenger/MessageHandler/JobRunner.php index bb3afe8..350e847 100644 --- a/Messenger/MessageHandler/JobRunner.php +++ b/Messenger/MessageHandler/JobRunner.php @@ -2,7 +2,7 @@ namespace RetailCrm\ServiceBundle\Messenger\MessageHandler; -use RetailCrm\ServiceBundle\Messenger\Message; +use RetailCrm\ServiceBundle\Messenger\CommandMessage; /** * Interface JobRunner @@ -12,7 +12,7 @@ use RetailCrm\ServiceBundle\Messenger\Message; interface JobRunner { /** - * @param Message $message + * @param CommandMessage $message */ - public function run(Message $message): void; + public function run(CommandMessage $message): void; } diff --git a/Messenger/MessageHandler/SimpleConsoleRunner.php b/Messenger/MessageHandler/SimpleConsoleRunner.php index 78d1eb1..0291d8d 100644 --- a/Messenger/MessageHandler/SimpleConsoleRunner.php +++ b/Messenger/MessageHandler/SimpleConsoleRunner.php @@ -3,12 +3,11 @@ namespace RetailCrm\ServiceBundle\Messenger\MessageHandler; use Psr\Log\LoggerInterface; -use RetailCrm\ServiceBundle\Messenger\Message; +use RetailCrm\ServiceBundle\Messenger\CommandMessage; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\HttpKernel\KernelInterface; -use Exception; /** * Class SimpleConsoleRunner @@ -42,7 +41,7 @@ class SimpleConsoleRunner implements JobRunner /** * {@inheritdoc} */ - public function run(Message $message): void + public function run(CommandMessage $message): void { $application = new Application($this->kernel); $application->setAutoExit(false); @@ -56,7 +55,11 @@ class SimpleConsoleRunner implements JobRunner ); $output = new BufferedOutput(); - $application->run($input, $output); + if ($application->run($input, $output) > 0) { + $this->logger->error($output->fetch()); + + return; + } echo $output->fetch(); } diff --git a/Resources/doc/Messenger.md b/Resources/doc/Messenger.md new file mode 100644 index 0000000..d68f900 --- /dev/null +++ b/Resources/doc/Messenger.md @@ -0,0 +1,60 @@ +### Messenger + +#### Messages + +The library provides a basic message for executing console commands as message handlers - `RetailCrm\ServiceBundle\Messenger\CommandMessage`. +This makes it easier to create new message types. For example: + +* Create your message + +```php + +namespace App\Message; + +use RetailCrm\ServiceBundle\Messenger\CommandMessage; + +class MyMessage extends CommandMessage +{ + public function __construct() + { + $this->commandName = \App\Command\MyCommand::getDefaultName(); + $this->options = ['optionName' => 'optionValue']; + $this->arguments = ['argumentName' => 'argumentValue']; + } +} + +``` + +* Add a message to a routing + +```yaml +# config/packages/messenger.yaml +framework: + messenger: + transports: + async: "%env(MESSENGER_TRANSPORT_DSN)%" + + routing: + 'App\Message\MyMessage': async +``` + +Now when sending this message through messenger (```$messageBus->dispatch(new MyMessage())```) the message handler will run the associated command + +#### Message handlers + +Two messages handlers are is supported: + +* all messages handling in one worker process +* each message is a processed in a separate process + +By default, messages will be processed in one process. To set up a handler to run in a separate process, add to the bundle config: + +```yaml +retail_crm_service: + messenger: + message_handler: in_new_process_runner + process_timeout: 60 +``` + +`process_timeout` - an optional parameter that only makes sense when `message_handler` is equal `in_new_process_runner` and determines the lifetime of the process. +By default, process timeout - 3600 (in seconds). diff --git a/Resources/doc/Requests.md b/Resources/doc/Requests.md new file mode 100644 index 0000000..a70866d --- /dev/null +++ b/Resources/doc/Requests.md @@ -0,0 +1,71 @@ +### Deserialize incoming requests + +#### Callbacks (form data) + +To automatically get the callback request parameter + +```php + +class AppController extends AbstractController +{ + public function activityAction(\App\Dto\Callback\Activity $activity): Response + { + // handle activity + } +} + +``` + +add to the config: + +```yaml +retail_crm_service: + request_schema: + callback: + supports: + - { type: App\Dto\Callback\Activity, params: ["activity"] } +``` + +request automatically will be deserialization to $activity. + +#### Body json content + +```php + +class AppController extends AbstractController +{ + public function someAction(\App\Dto\Body $activity): Response + { + // handle activity + } +} + +``` + +add to the config: + +```yaml +retail_crm_service: + request_schema: + client: + supports: + - App\Dto\Body +``` + +#### Serializers +At this time supported [Symfony serializer](https://symfony.com/doc/current/components/serializer.html) and [JMS serializer](https://jmsyst.com/libs/serializer). +By default, the library using a Symfony serializer. For use JMS install JMS serializer bundle - `composer require jms/serializer-bundle` +You can explicitly specify the type of serializer used for request schema: + +```yaml +retail_crm_service: + request_schema: + client: + supports: + # types + serializer: retail_crm_service.symfony_serializer.adapter # or retail_crm_service.jms_serializer.adapter + callback: + supports: + # types + serializer: retail_crm_service.jms_serializer.adapter # or retail_crm_service.symfony_serializer.adapter +``` diff --git a/Resources/doc/Security.md b/Resources/doc/Security.md new file mode 100644 index 0000000..9de58c1 --- /dev/null +++ b/Resources/doc/Security.md @@ -0,0 +1,70 @@ +### Authentication + +Example security configuration: + +```yaml +security: + providers: + client: + entity: + class: 'App\Entity\Connection' # must implements UserInterface + property: 'clientId' + firewalls: + api: + pattern: ^/api + provider: client + anonymous: ~ + lazy: true + stateless: false + guard: + authenticators: + - RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator + callback: + pattern: ^/callback + provider: client + anonymous: ~ + lazy: true + stateless: true + guard: + authenticators: + - RetailCrm\ServiceBundle\Security\CallbackClientAuthenticator + main: + anonymous: true + lazy: true + + access_control: + - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } # login for programmatically authentication user + - { path: ^/api, roles: ROLE_USER } + - { path: ^/callback, roles: ROLE_USER } +``` + +To authenticate the user after creating it, you can use the following code + +```php + +use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; +use RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +class AppController extends AbstractController +{ + public function someAction( + Request $request, + GuardAuthenticatorHandler $guardAuthenticatorHandler, + FrontApiClientAuthenticator $frontApiClientAuthenticator, + ConnectionManager $manager + ): Response { + $user = $manager->getUser(); // getting user + + $guardAuthenticatorHandler->authenticateUserAndHandleSuccess( + $user, + $request, + $frontApiClientAuthenticator, + 'api' + ); + // ... + } +} + +``` diff --git a/Resources/doc/index.md b/Resources/doc/index.md index 443faab..53c0418 100644 --- a/Resources/doc/index.md +++ b/Resources/doc/index.md @@ -2,8 +2,6 @@ `composer require retailcrm/service-bundle` -## Usage - Enable bundle in `config/bundles.php`: ```php @@ -23,147 +21,10 @@ retail_crm_service: request_schema: callback: ~ client: ~ + messenger: ~ ``` -### Deserializing incoming requests - -#### Callbacks (form data) - -To automatically get the callback request parameter - -```php - -class AppController extends AbstractController -{ - public function activityAction(\App\Dto\Callback\Activity $activity): Response - { - // handle activity - } -} - -``` - -add to the config: - -```yaml -retail_crm_service: - request_schema: - callback: - supports: - - { type: App\Dto\Callback\Activity, params: ["activity"] } -``` - -request automatically will be deserialization to $activity. - -#### Body json content - -```php - -class AppController extends AbstractController -{ - public function someAction(\App\Dto\Body $activity): Response - { - // handle activity - } -} - -``` - -add to the config: - -```yaml -retail_crm_service: - request_schema: - client: - supports: - - App\Dto\Body -``` - -#### Serializers -At this time supported [Symfony serializer](https://symfony.com/doc/current/components/serializer.html) and [JMS serializer](https://jmsyst.com/libs/serializer). -By default, the library using a Symfony serializer. For use JMS install JMS serializer bundle - `composer require jms/serializer-bundle` -You can explicitly specify the type of serializer used for request schema: - -```yaml -retail_crm_service: - request_schema: - client: - supports: - # types - serializer: retail_crm_service.symfony_serializer.adapter # or retail_crm_service.jms_serializer.adapter - callback: - supports: - # types - serializer: retail_crm_service.jms_serializer.adapter # or retail_crm_service.symfony_serializer.adapter -``` - -### Authentication - -Example security configuration: - -```yaml -security: - providers: - client: - entity: - class: 'App\Entity\Connection' # must implements UserInterface - property: 'clientId' - firewalls: - api: - pattern: ^/api - provider: client - anonymous: ~ - lazy: true - stateless: false - guard: - authenticators: - - RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator - callback: - pattern: ^/callback - provider: client - anonymous: ~ - lazy: true - stateless: true - guard: - authenticators: - - RetailCrm\ServiceBundle\Security\CallbackClientAuthenticator - main: - anonymous: true - lazy: true - - access_control: - - { path: ^/api/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } # login for programmatically authentication user - - { path: ^/api, roles: ROLE_USER } - - { path: ^/callback, roles: ROLE_USER } -``` - -To authenticate the user after creating it, you can use the following code - -```php - -use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; -use RetailCrm\ServiceBundle\Security\FrontApiClientAuthenticator; -use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpFoundation\Response; - -class AppController extends AbstractController -{ - public function someAction( - Request $request, - GuardAuthenticatorHandler $guardAuthenticatorHandler, - FrontApiClientAuthenticator $frontApiClientAuthenticator, - ConnectionManager $manager - ): Response { - $user = $manager->getUser(); // getting user - - $guardAuthenticatorHandler->authenticateUserAndHandleSuccess( - $user, - $request, - $frontApiClientAuthenticator, - 'api' - ); - // ... - } -} - -``` +## Usage +* [Handling incoming requests data](./Requests.md) +* [Security](./Security.md) +* [Messenger](./Messenger.md) diff --git a/Tests/Fixtures/App/Kernel.php b/Tests/Fixtures/App/Kernel.php new file mode 100644 index 0000000..d10c683 --- /dev/null +++ b/Tests/Fixtures/App/Kernel.php @@ -0,0 +1,33 @@ +register(TestCommand::class, TestCommand::class) + ->addTag('console.command', ['command' => TestCommand::getDefaultName()]) + ; + + $container->setParameter('kernel.project_dir', __DIR__ . '/..'); + } + +// public function registerContainerConfiguration(LoaderInterface $loader) +// { +// } +} diff --git a/Tests/Fixtures/App/TestCommand.php b/Tests/Fixtures/App/TestCommand.php new file mode 100644 index 0000000..a47c371 --- /dev/null +++ b/Tests/Fixtures/App/TestCommand.php @@ -0,0 +1,36 @@ +addArgument( + 'argument', + InputArgument::REQUIRED + ) + ->addOption( + 'option', + null, + InputOption::VALUE_REQUIRED + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + echo self::$defaultName . ' ' . $input->getArgument('argument') . ' ' . $input->getOption('option'); + + return Command::SUCCESS; + } +} diff --git a/Tests/Fixtures/App/TestCommandMessage.php b/Tests/Fixtures/App/TestCommandMessage.php new file mode 100644 index 0000000..e03e282 --- /dev/null +++ b/Tests/Fixtures/App/TestCommandMessage.php @@ -0,0 +1,15 @@ +commandName = 'test'; + $this->arguments = ['argument' => 'test']; + $this->options = ['option' => 'test']; + } +} diff --git a/Tests/Fixtures/bin/console b/Tests/Fixtures/bin/console new file mode 100644 index 0000000..b5a1057 --- /dev/null +++ b/Tests/Fixtures/bin/console @@ -0,0 +1,9 @@ +#!/usr/bin/env php +run($input); diff --git a/Tests/Messenger/MessageHandler/InNewProcessRunnerTest.php b/Tests/Messenger/MessageHandler/InNewProcessRunnerTest.php new file mode 100644 index 0000000..38b8507 --- /dev/null +++ b/Tests/Messenger/MessageHandler/InNewProcessRunnerTest.php @@ -0,0 +1,27 @@ + 'test']); + } + + public function testRun(): void + { + $runner = new InNewProcessRunner(new NullLogger, self::$kernel); + + ob_clean(); + ob_start(); + $runner->run(new TestCommandMessage()); + + static::assertEquals('test test test', ob_get_clean()); + } +} diff --git a/Tests/Messenger/MessageHandler/SimpleConsoleRunnerTest.php b/Tests/Messenger/MessageHandler/SimpleConsoleRunnerTest.php new file mode 100644 index 0000000..f279d52 --- /dev/null +++ b/Tests/Messenger/MessageHandler/SimpleConsoleRunnerTest.php @@ -0,0 +1,27 @@ + 'test']); + } + + public function testRun(): void + { + $runner = new SimpleConsoleRunner(new NullLogger, self::$kernel); + + ob_clean(); + ob_start(); + $runner->run(new TestCommandMessage()); + + static::assertEquals('test test test', ob_get_clean()); + } +} diff --git a/composer.json b/composer.json index 44539dd..c90c1ab 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ "symfony/security-guard": "^4.0|^5.0", "symfony/console": "^5.2", "symfony/messenger": "^5.2", - "symfony/process": "^5.2" + "symfony/process": "^5.2", + "symfony/event-dispatcher": "^5.2" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index ada38ee..56d86c0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,6 +10,9 @@ stopOnFailure="false" bootstrap="vendor/autoload.php" > + + + ./