feature #26648 [Messenger] Added a middleware that validates messages (Nyholm)

This PR was squashed before being merged into the 4.1-dev branch (closes #26648).

Discussion
----------

[Messenger] Added a middleware that validates messages

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets |
| License       | MIT
| Doc PR        | coming

This is inspired by [Fervo](https://github.com/fervo/ValidatedMessage).  It runs the validator over messages implementing the `ValidatedMessageInterface`.

Commits
-------

43a51714d4 [Messenger] Added a middleware that validates messages
This commit is contained in:
Samuel ROZE 2018-04-03 21:17:16 +01:00
commit 56cd3d2f31
11 changed files with 155 additions and 4 deletions

View File

@ -1005,10 +1005,14 @@ class Configuration implements ConfigurationInterface
->arrayNode('middlewares')
->addDefaultsIfNotSet()
->children()
->arrayNode('doctrine_transaction')
->canBeEnabled()
->children()
->scalarNode('entity_manager_name')->info('The name of the entity manager to use')->defaultNull()->end()
->arrayNode('doctrine_transaction')
->canBeEnabled()
->children()
->scalarNode('entity_manager_name')->info('The name of the entity manager to use')->defaultNull()->end()
->end()
->end()
->arrayNode('validation')
->{!class_exists(FullStack::class) && class_exists(Validation::class) ? 'canBeDisabled' : 'canBeEnabled'}()
->end()
->end()
->end()

View File

@ -1459,6 +1459,14 @@ class FrameworkExtension extends Extension
} else {
$container->removeDefinition('messenger.middleware.doctrine_transaction');
}
if ($config['middlewares']['validation']['enabled']) {
if (!$container->has('validator')) {
throw new LogicException('The Validation middleware is only available when the Validator component is installed and enabled. Try running "composer require symfony/validator".');
}
} else {
$container->removeDefinition('messenger.middleware.validator');
}
}
private function registerCacheConfiguration(array $config, ContainerBuilder $container)

View File

@ -25,6 +25,12 @@
<tag name="message_bus_middleware" priority="-10" />
</service>
<service id="messenger.middleware.validator" class="Symfony\Component\Messenger\Middleware\ValidationMiddleware">
<argument type="service" id="validator" />
<tag name="message_bus_middleware" priority="100" />
</service>
<service id="messenger.middleware.doctrine_transaction" class="Symfony\Bridge\Doctrine\Messenger\DoctrineTransactionMiddleware">
<argument type="service" id="doctrine" />
<argument /> <!-- Entity manager name -->

View File

@ -371,6 +371,7 @@
<xsd:complexType name="messenger_middleware">
<xsd:sequence>
<xsd:element name="doctrine-transaction" type="messenger_doctrine_transaction" minOccurs="0" maxOccurs="1" />
<xsd:element name="validation" type="messenger_validation" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
@ -378,4 +379,8 @@
<xsd:attribute name="entity-manager-name" type="xsd:string" />
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
<xsd:complexType name="messenger_validation">
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
</xsd:schema>

View File

@ -258,6 +258,9 @@ class ConfigurationTest extends TestCase
'enabled' => false,
'entity_manager_name' => null,
),
'validation' => array(
'enabled' => false,
),
),
),
);

View File

@ -0,0 +1,11 @@
<?php
$container->loadFromExtension('framework', array(
'messenger' => array(
'middlewares' => array(
'validation' => array(
'enabled' => false,
),
),
),
));

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
<framework:config>
<framework:messenger>
<framework:middlewares>
<framework:validation enabled="false"/>
</framework:middlewares>
</framework:messenger>
</framework:config>
</container>

View File

@ -0,0 +1,5 @@
framework:
messenger:
middlewares:
validation:
enabled: false

View File

@ -45,6 +45,7 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
use Symfony\Component\Translation\DependencyInjection\TranslatorPass;
use Symfony\Component\Validator\DependencyInjection\AddConstraintValidatorsPass;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Workflow;
abstract class FrameworkExtensionTest extends TestCase
@ -532,6 +533,16 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertEquals('foobar', $def->getArgument(1));
}
public function testMessengerValidationDisabled()
{
if (!class_exists(Validation::class)) {
self::markTestSkipped('Skipping tests since Validator component is not installed');
}
$container = $this->createContainerFromFile('messenger_validation');
$this->assertFalse($container->hasDefinition('messenger.middleware.validator'));
}
public function testTranslator()
{
$container = $this->createContainerFromFile('full');

View File

@ -0,0 +1,44 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Exception;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class ValidationFailedException extends \RuntimeException implements ExceptionInterface
{
private $violations;
private $violatingMessage;
/**
* @param object $violatingMessage
*/
public function __construct($violatingMessage, ConstraintViolationListInterface $violations)
{
$this->violatingMessage = $violatingMessage;
$this->violations = $violations;
parent::__construct(sprintf('Message of type "%s" failed validation.', get_class($this->violatingMessage)));
}
public function getViolatingMessage()
{
return $this->violatingMessage;
}
public function getViolations(): ConstraintViolationListInterface
{
return $this->violations;
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Messenger\Middleware;
use Symfony\Component\Messenger\Exception\ValidationFailedException;
use Symfony\Component\Messenger\MiddlewareInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class ValidationMiddleware implements MiddlewareInterface
{
private $validator;
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
public function handle($message, callable $next)
{
$violations = $this->validator->validate($message);
if (count($violations)) {
throw new ValidationFailedException($message, $violations);
}
return $next($message);
}
}