tests and doc
This commit is contained in:
parent
4ec3731281
commit
e7d19dcad1
17 changed files with 375 additions and 159 deletions
|
@ -7,7 +7,7 @@ namespace RetailCrm\ServiceBundle\Messenger;
|
|||
*
|
||||
* @package RetailCrm\ServiceBundle\Messenger
|
||||
*/
|
||||
abstract class Message
|
||||
abstract class CommandMessage
|
||||
{
|
||||
/** @var string */
|
||||
protected $commandName;
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
60
Resources/doc/Messenger.md
Normal file
60
Resources/doc/Messenger.md
Normal 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
71
Resources/doc/Requests.md
Normal 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
70
Resources/doc/Security.md
Normal 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'
|
||||
);
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
```
|
|
@ -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)
|
||||
|
|
33
Tests/Fixtures/App/Kernel.php
Normal file
33
Tests/Fixtures/App/Kernel.php
Normal 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)
|
||||
// {
|
||||
// }
|
||||
}
|
36
Tests/Fixtures/App/TestCommand.php
Normal file
36
Tests/Fixtures/App/TestCommand.php
Normal 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;
|
||||
}
|
||||
}
|
15
Tests/Fixtures/App/TestCommandMessage.php
Normal file
15
Tests/Fixtures/App/TestCommandMessage.php
Normal 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'];
|
||||
}
|
||||
}
|
9
Tests/Fixtures/bin/console
Normal file
9
Tests/Fixtures/bin/console
Normal 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);
|
27
Tests/Messenger/MessageHandler/InNewProcessRunnerTest.php
Normal file
27
Tests/Messenger/MessageHandler/InNewProcessRunnerTest.php
Normal 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());
|
||||
}
|
||||
}
|
27
Tests/Messenger/MessageHandler/SimpleConsoleRunnerTest.php
Normal file
27
Tests/Messenger/MessageHandler/SimpleConsoleRunnerTest.php
Normal 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());
|
||||
}
|
||||
}
|
|
@ -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": {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue