[DependencyInjection] Use a service locator in AddConstraintValidatorsPass

This commit is contained in:
Guilhem Niot 2017-02-23 12:19:48 +01:00 committed by Fabien Potencier
parent 7f27787dd0
commit 597b6bcab6
8 changed files with 58 additions and 66 deletions

View File

@ -143,6 +143,11 @@ FrameworkBundle
deprecated and will be removed in 4.0. Use the `Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass` deprecated and will be removed in 4.0. Use the `Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass`
class instead. class instead.
* The `ConstraintValidatorFactory::$validators` and `$container` properties
have been deprecated and will be removed in 4.0.
* Extending `ConstraintValidatorFactory` is deprecated and won't be supported in 4.0.
HttpKernel HttpKernel
----------- -----------

View File

@ -202,6 +202,11 @@ FrameworkBundle
removed. Use the `Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass` removed. Use the `Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass`
class instead. class instead.
* The `ConstraintValidatorFactory::$validators` and `$container` properties
have been removed.
* Extending `ConstraintValidatorFactory` is not supported anymore.
HttpFoundation HttpFoundation
--------------- ---------------
@ -243,7 +248,7 @@ HttpKernel
* The `Psr6CacheClearer::addPool()` method has been removed. Pass an array of pools indexed * The `Psr6CacheClearer::addPool()` method has been removed. Pass an array of pools indexed
by name to the constructor instead. by name to the constructor instead.
* The `LazyLoadingFragmentHandler::addRendererService()` method has been removed. * The `LazyLoadingFragmentHandler::addRendererService()` method has been removed.
* The `X-Status-Code` header method of setting a custom status code in the * The `X-Status-Code` header method of setting a custom status code in the
@ -310,7 +315,7 @@ Translation
TwigBundle TwigBundle
---------- ----------
* The `ContainerAwareRuntimeLoader` class has been removed. Use the * The `ContainerAwareRuntimeLoader` class has been removed. Use the
Twig `Twig_ContainerRuntimeLoader` class instead. Twig `Twig_ContainerRuntimeLoader` class instead.
TwigBridge TwigBridge

View File

@ -17,9 +17,10 @@ CHANGELOG
* Deprecated `FormPass`, use `Symfony\Component\Form\DependencyInjection\FormPass` instead * Deprecated `FormPass`, use `Symfony\Component\Form\DependencyInjection\FormPass` instead
* Deprecated `SessionListener` * Deprecated `SessionListener`
* Deprecated `TestSessionListener` * Deprecated `TestSessionListener`
* Deprecated `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass`. * Deprecated `Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass`.
Use `Symfony\Component\Console\DependencyInjection\ConfigCachePass` instead. Use `Symfony\Component\Console\DependencyInjection\ConfigCachePass` instead.
* Deprecated `PropertyInfoPass`, use `Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass` instead * Deprecated `PropertyInfoPass`, use `Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass` instead
* Deprecated extending `ConstraintValidatorFactory`
3.2.0 3.2.0
----- -----
@ -31,7 +32,7 @@ CHANGELOG
* Removed `symfony/asset` from the list of required dependencies in `composer.json` * Removed `symfony/asset` from the list of required dependencies in `composer.json`
* The `Resources/public/images/*` files have been removed. * The `Resources/public/images/*` files have been removed.
* The `Resources/public/css/*.css` files have been removed (they are now inlined in TwigBundle). * The `Resources/public/css/*.css` files have been removed (they are now inlined in TwigBundle).
* Added possibility to prioritize form type extensions with `'priority'` attribute on tags `form.type_extension` * Added possibility to prioritize form type extensions with `'priority'` attribute on tags `form.type_extension`
3.1.0 3.1.0
----- -----

View File

@ -11,9 +11,10 @@
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Reference;
class AddConstraintValidatorsPass implements CompilerPassInterface class AddConstraintValidatorsPass implements CompilerPassInterface
{ {
@ -25,23 +26,19 @@ class AddConstraintValidatorsPass implements CompilerPassInterface
$validators = array(); $validators = array();
foreach ($container->findTaggedServiceIds('validator.constraint_validator') as $id => $attributes) { foreach ($container->findTaggedServiceIds('validator.constraint_validator') as $id => $attributes) {
if (isset($attributes[0]['alias'])) {
$validators[$attributes[0]['alias']] = $id;
}
$definition = $container->getDefinition($id); $definition = $container->getDefinition($id);
if (!$definition->isPublic()) {
throw new InvalidArgumentException(sprintf('The service "%s" must be public as it can be lazy-loaded.', $id));
}
if ($definition->isAbstract()) { if ($definition->isAbstract()) {
throw new InvalidArgumentException(sprintf('The service "%s" must not be abstract as it can be lazy-loaded.', $id)); continue;
} }
$validators[$definition->getClass()] = $id; if (isset($attributes[0]['alias'])) {
$validators[$attributes[0]['alias']] = new Reference($id);
}
$validators[$definition->getClass()] = new Reference($id);
} }
$container->getDefinition('validator.validator_factory')->replaceArgument(1, $validators); $container->getDefinition('validator.validator_factory')->replaceArgument(0, new ServiceLocatorArgument($validators));
} }
} }

View File

@ -57,8 +57,7 @@
</service> </service>
<service id="validator.validator_factory" class="Symfony\Bundle\FrameworkBundle\Validator\ConstraintValidatorFactory" public="false"> <service id="validator.validator_factory" class="Symfony\Bundle\FrameworkBundle\Validator\ConstraintValidatorFactory" public="false">
<argument type="service" id="service_container" /> <argument type="service-locator" /> <!-- Constraint validators locator -->
<argument type="collection" />
</service> </service>
<service id="validator.expression" class="Symfony\Component\Validator\Constraints\ExpressionValidator"> <service id="validator.expression" class="Symfony\Component\Validator\Constraints\ExpressionValidator">

View File

@ -13,56 +13,34 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class AddConstraintValidatorsPassTest extends TestCase class AddConstraintValidatorsPassTest extends TestCase
{ {
public function testThatConstraintValidatorServicesAreProcessed() public function testThatConstraintValidatorServicesAreProcessed()
{ {
$services = array( $container = new ContainerBuilder();
'my_constraint_validator_service1' => array(0 => array('alias' => 'my_constraint_validator_alias1')), $validatorFactory = $container->register('validator.validator_factory')
'my_constraint_validator_service2' => array(), ->setArguments(array(new ServiceLocatorArgument()));
);
$validatorFactoryDefinition = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->getMock(); $container->register('my_constraint_validator_service1', Validator1::class)
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('findTaggedServiceIds', 'getDefinition', 'hasDefinition'))->getMock(); ->addTag('validator.constraint_validator', array('alias' => 'my_constraint_validator_alias1'));
$container->register('my_constraint_validator_service2', Validator2::class)
$validatorDefinition1 = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->setMethods(array('getClass'))->getMock(); ->addTag('validator.constraint_validator');
$validatorDefinition2 = $this->getMockBuilder('Symfony\Component\DependencyInjection\Definition')->setMethods(array('getClass'))->getMock(); $container->register('my_abstract_constraint_validator')
->setAbstract(true)
$validatorDefinition1->expects($this->atLeastOnce()) ->addTag('validator.constraint_validator');
->method('getClass')
->willReturn('My\Fully\Qualified\Class\Named\Validator1');
$validatorDefinition2->expects($this->atLeastOnce())
->method('getClass')
->willReturn('My\Fully\Qualified\Class\Named\Validator2');
$container->expects($this->any())
->method('getDefinition')
->with($this->anything())
->will($this->returnValueMap(array(
array('my_constraint_validator_service1', $validatorDefinition1),
array('my_constraint_validator_service2', $validatorDefinition2),
array('validator.validator_factory', $validatorFactoryDefinition),
)));
$container->expects($this->atLeastOnce())
->method('findTaggedServiceIds')
->will($this->returnValue($services));
$container->expects($this->atLeastOnce())
->method('hasDefinition')
->with('validator.validator_factory')
->will($this->returnValue(true));
$validatorFactoryDefinition->expects($this->once())
->method('replaceArgument')
->with(1, array(
'My\Fully\Qualified\Class\Named\Validator1' => 'my_constraint_validator_service1',
'my_constraint_validator_alias1' => 'my_constraint_validator_service1',
'My\Fully\Qualified\Class\Named\Validator2' => 'my_constraint_validator_service2',
));
$addConstraintValidatorsPass = new AddConstraintValidatorsPass(); $addConstraintValidatorsPass = new AddConstraintValidatorsPass();
$addConstraintValidatorsPass->process($container); $addConstraintValidatorsPass->process($container);
$this->assertEquals(new ServiceLocatorArgument(array(
Validator1::class => new Reference('my_constraint_validator_service1'),
'my_constraint_validator_alias1' => new Reference('my_constraint_validator_service1'),
Validator2::class => new Reference('my_constraint_validator_service2'),
)), $validatorFactory->getArgument(0));
} }
public function testThatCompilerPassIsIgnoredIfThereIsNoConstraintValidatorFactoryDefinition() public function testThatCompilerPassIsIgnoredIfThereIsNoConstraintValidatorFactoryDefinition()

View File

@ -11,7 +11,7 @@
namespace Symfony\Bundle\FrameworkBundle\Validator; namespace Symfony\Bundle\FrameworkBundle\Validator;
use Symfony\Component\DependencyInjection\ContainerInterface; use Psr\Container\ContainerInterface;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorFactoryInterface; use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\ConstraintValidatorInterface;
@ -37,6 +37,8 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
* } * }
* *
* @author Kris Wallsmith <kris@symfony.com> * @author Kris Wallsmith <kris@symfony.com>
*
* @final since version 3.3
*/ */
class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
{ {
@ -70,11 +72,15 @@ class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
$name = $constraint->validatedBy(); $name = $constraint->validatedBy();
if (!isset($this->validators[$name])) { if (!isset($this->validators[$name])) {
if (!class_exists($name)) { if ($this->container->has($name)) {
throw new ValidatorException(sprintf('Constraint validator "%s" does not exist or it is not enabled. Check the "validatedBy" method in your constraint class "%s".', $name, get_class($constraint))); $this->validators[$name] = $this->container->get($name);
} } else {
if (!class_exists($name)) {
throw new ValidatorException(sprintf('Constraint validator "%s" does not exist or it is not enabled. Check the "validatedBy" method in your constraint class "%s".', $name, get_class($constraint)));
}
$this->validators[$name] = new $name(); $this->validators[$name] = new $name();
}
} elseif (is_string($this->validators[$name])) { } elseif (is_string($this->validators[$name])) {
$this->validators[$name] = $this->container->get($this->validators[$name]); $this->validators[$name] = $this->container->get($this->validators[$name]);
} }

View File

@ -49,7 +49,7 @@
"symfony/serializer": "~3.3", "symfony/serializer": "~3.3",
"symfony/translation": "~2.8|~3.0", "symfony/translation": "~2.8|~3.0",
"symfony/templating": "~2.8|~3.0", "symfony/templating": "~2.8|~3.0",
"symfony/validator": "~3.2", "symfony/validator": "~3.3",
"symfony/yaml": "~3.2", "symfony/yaml": "~3.2",
"symfony/property-info": "~3.3", "symfony/property-info": "~3.3",
"doctrine/annotations": "~1.0", "doctrine/annotations": "~1.0",
@ -65,7 +65,8 @@
"symfony/console": "<3.3", "symfony/console": "<3.3",
"symfony/serializer": "<3.3", "symfony/serializer": "<3.3",
"symfony/form": "<3.3", "symfony/form": "<3.3",
"symfony/property-info": "<3.3" "symfony/property-info": "<3.3",
"symfony/validator": "<3.3"
}, },
"suggest": { "suggest": {
"ext-apcu": "For best performance of the system caches", "ext-apcu": "For best performance of the system caches",