1
0
Fork 0
mirror of synced 2025-04-05 06:03:34 +03:00

tests and doc

This commit is contained in:
Akolzin Dmitry 2021-03-30 14:08:11 +03:00
parent 4ec3731281
commit e7d19dcad1
17 changed files with 375 additions and 159 deletions

View file

@ -7,7 +7,7 @@ namespace RetailCrm\ServiceBundle\Messenger;
*
* @package RetailCrm\ServiceBundle\Messenger
*/
abstract class Message
abstract class CommandMessage
{
/** @var string */
protected $commandName;

View file

@ -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);
}

View file

@ -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) {

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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).

71
Resources/doc/Requests.md Normal file
View file

@ -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
```

70
Resources/doc/Security.md Normal file
View file

@ -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'
);
// ...
}
}
```

View file

@ -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)

View file

@ -0,0 +1,33 @@
<?php
namespace RetailCrm\ServiceBundle\Tests\Fixtures\App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class Kernel extends \Symfony\Component\HttpKernel\Kernel
{
use MicroKernelTrait;
public function registerBundles()
{
return [
new \Symfony\Bundle\FrameworkBundle\FrameworkBundle()
];
}
protected function configureContainer(ContainerBuilder $container/*, LoaderInterface $loader*/): void
{
$container
->register(TestCommand::class, TestCommand::class)
->addTag('console.command', ['command' => TestCommand::getDefaultName()])
;
$container->setParameter('kernel.project_dir', __DIR__ . '/..');
}
// public function registerContainerConfiguration(LoaderInterface $loader)
// {
// }
}

View file

@ -0,0 +1,36 @@
<?php
namespace RetailCrm\ServiceBundle\Tests\Fixtures\App;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class TestCommand extends Command
{
protected static $defaultName = 'test';
protected function configure(): void
{
$this
->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;
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace RetailCrm\ServiceBundle\Tests\Fixtures\App;
use RetailCrm\ServiceBundle\Messenger\CommandMessage;
class TestCommandMessage extends CommandMessage
{
public function __construct()
{
$this->commandName = 'test';
$this->arguments = ['argument' => 'test'];
$this->options = ['option' => 'test'];
}
}

View file

@ -0,0 +1,9 @@
#!/usr/bin/env php
<?php
require_once __DIR__ . '/../../../vendor/autoload.php';
$input = new Symfony\Component\Console\Input\ArgvInput();
$kernel = new RetailCrm\ServiceBundle\Tests\Fixtures\App\Kernel('test', true);
$application = new Symfony\Bundle\FrameworkBundle\Console\Application($kernel);
$application->run($input);

View file

@ -0,0 +1,27 @@
<?php
namespace RetailCrm\ServiceBundle\Tests\Messenger\MessageHandler;
use Psr\Log\NullLogger;
use RetailCrm\ServiceBundle\Messenger\MessageHandler\InNewProcessRunner;
use RetailCrm\ServiceBundle\Tests\Fixtures\App\TestCommandMessage;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class InNewProcessRunnerTest extends KernelTestCase
{
protected function setUp(): void
{
self::bootKernel(['environment' => '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());
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace RetailCrm\ServiceBundle\Tests\Messenger\MessageHandler;
use Psr\Log\NullLogger;
use RetailCrm\ServiceBundle\Messenger\MessageHandler\SimpleConsoleRunner;
use RetailCrm\ServiceBundle\Tests\Fixtures\App\TestCommandMessage;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class SimpleConsoleRunnerTest extends KernelTestCase
{
protected function setUp(): void
{
self::bootKernel(['environment' => '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());
}
}

View file

@ -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": {

View file

@ -10,6 +10,9 @@
stopOnFailure="false"
bootstrap="vendor/autoload.php"
>
<php>
<server name="KERNEL_CLASS" value="RetailCrm\ServiceBundle\Tests\Fixtures\App\Kernel" />
</php>
<coverage>
<include>
<directory>./</directory>