diff --git a/UPGRADE-2.5.md b/UPGRADE-2.5.md index e3b581b5b9dfd..56b94f862a588 100644 --- a/UPGRADE-2.5.md +++ b/UPGRADE-2.5.md @@ -56,10 +56,11 @@ Validator After: - Default email validation is now done via a simple regex which may cause invalid emails (not RFC compilant) to be + Default email validation is now done via a simple regex which may cause invalid emails (not RFC compilant) to be valid. This is the default behaviour. Strict email validation has to be explicitly activated in the configuration file by adding + ``` framework: //... @@ -68,7 +69,119 @@ Validator //... ``` + Also you have to add to your composer.json: + ``` "egulias/email-validator": "1.1.*" ``` + + * `ClassMetadata::getGroupSequence()` now returns `GroupSequence` instances + instead of an array. The sequence implements `\Traversable`, `\ArrayAccess` + and `\Countable`, so in most cases you should be fine. If you however use the + sequence with PHP's `array_*()` functions, you should cast it to an array + first using `iterator_to_array()`: + + Before: + + ``` + $sequence = $metadata->getGroupSequence(); + $result = array_map($callback, $sequence); + ``` + + After: + + ``` + $sequence = iterator_to_array($metadata->getGroupSequence()); + $result = array_map($callback, $sequence); + ``` + + * The array type hint in `ClassMetadata::setGroupSequence()` was removed. If + you overwrite this method, make sure to remove the type hint as well. The + method should now accept `GroupSequence` instances just as well as arrays. + + Before: + + ``` + public function setGroupSequence(array $groups) + { + // ... + } + ``` + + After: + + ``` + public function setGroupSequence($groupSequence) + { + // ... + } + ``` + + * The validation engine in `Symfony\Component\Validator\Validator` was replaced + by a new one in `Symfony\Component\Validator\Validator\RecursiveValidator`. + With that change, several classes were deprecated that will be removed in + Symfony 3.0. Also, the API of the validator was slightly changed. More + details about that can be found in UPGRADE-3.0. + + You can choose the desired API via the new "api" entry in + app/config/config.yml: + + ``` + framework: + validation: + enabled: true + api: auto + ``` + + When running PHP 5.3.9 or higher, Symfony will then use an implementation + that supports both the old API and the new one: + + ``` + framework: + validation: + enabled: true + api: 2.5-bc + ``` + + When running PHP lower than 5.3.9, that compatibility layer is not supported. + On those versions, the old implementation will be used instead: + + ``` + framework: + validation: + enabled: true + api: 2.4 + ``` + + If you develop a new application that doesn't rely on the old API, you can + also set the API to 2.5. In that case, the backwards compatibility layer + will not be activated: + + ``` + framework: + validation: + enabled: true + api: 2.5 + ``` + + When using the validator outside of the Symfony full-stack framework, the + desired API can be selected using `setApiVersion()` on the validator builder: + + ``` + // Previous implementation + $validator = Validation::createValidatorBuilder() + ->setApiVersion(Validation::API_VERSION_2_4) + ->getValidator(); + + // New implementation with backwards compatibility support + $validator = Validation::createValidatorBuilder() + ->setApiVersion(Validation::API_VERSION_2_5_BC) + ->getValidator(); + + // New implementation without backwards compatibility support + $validator = Validation::createValidatorBuilder() + ->setApiVersion(Validation::API_VERSION_2_5) + ->getValidator(); + ``` + diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 96907164a541c..e9468f9ed1892 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -647,6 +647,309 @@ UPGRADE FROM 2.x to 3.0 } ``` + * The interface `ValidatorInterface` was replaced by the more powerful + interface `Validator\ValidatorInterface`. The signature of the `validate()` + method is slightly different in that interface and accepts a value, zero + or more constraints and validation group. It replaces both + `validate()` and `validateValue()` in the previous interface. + + Before: + + ``` + $validator->validate($object, 'Strict'); + + $validator->validateValue($value, new NotNull()); + ``` + + After: + + ``` + $validator->validate($object, null, 'Strict'); + + $validator->validate($value, new NotNull()); + ``` + + Apart from this change, the new methods `startContext()` and `inContext()` + were added. The first of them allows to run multiple validations in the + same context and aggregate their violations: + + ``` + $violations = $validator->startContext() + ->atPath('firstName')->validate($firstName, new NotNull()) + ->atPath('age')->validate($age, new Type('integer')) + ->getViolations(); + ``` + + The second allows to run validation in an existing context. This is + especially useful when calling the validator from within constraint + validators: + + ``` + $validator->inContext($context)->validate($object); + ``` + + Instead of a `Validator`, the validator builder now returns a + `Validator\RecursiveValidator` instead. + + * The interface `ValidationVisitorInterface` and its implementation + `ValidationVisitor` were removed. The implementation of the visitor pattern + was flawed. Fixing that implementation would have drastically slowed down + the validator execution, so the visitor was removed completely instead. + + Along with the visitor, the method `accept()` was removed from + `MetadataInterface`. + + * The interface `MetadataInterface` was moved to the `Mapping` namespace. + + Before: + + ``` + use Symfony\Component\Validator\MetadataInterface; + ``` + + After: + + ``` + use Symfony\Component\Validator\Mapping\MetadataInterface; + ``` + + The methods `getCascadingStrategy()` and `getTraversalStrategy()` were + added to the interface. The first method should return a bit mask of the + constants in class `CascadingStrategy`. The second should return a bit + mask of the constants in `TraversalStrategy`. + + Example: + + ``` + use Symfony\Component\Validator\Mapping\TraversalStrategy; + + public function getTraversalStrategy() + { + return TraversalStrategy::TRAVERSE; + } + ``` + + * The interface `PropertyMetadataInterface` was moved to the `Mapping` + namespace. + + Before: + + ``` + use Symfony\Component\Validator\PropertyMetadataInterface; + ``` + + After: + + ``` + use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; + ``` + + * The interface `PropertyMetadataContainerInterface` was moved to the `Mapping` + namespace and renamed to `ClassMetadataInterface`. + + Before: + + ``` + use Symfony\Component\Validator\PropertyMetadataContainerInterface; + ``` + + After: + + ``` + use Symfony\Component\Validator\Mapping\ClassMetadataInterface; + ``` + + The interface now contains four additional methods: + + * `getConstrainedProperties()` + * `hasGroupSequence()` + * `getGroupSequence()` + * `isGroupSequenceProvider()` + + See the inline documentation of these methods for more information. + + * The interface `ClassBasedInterface` was removed. You should use + `Mapping\ClassMetadataInterface` instead: + + Before: + + ``` + use Symfony\Component\Validator\ClassBasedInterface; + + class MyClassMetadata implements ClassBasedInterface + { + // ... + } + ``` + + After: + + ``` + use Symfony\Component\Validator\Mapping\ClassMetadataInterface; + + class MyClassMetadata implements ClassMetadataInterface + { + // ... + } + ``` + + * The class `ElementMetadata` was renamed to `GenericMetadata`. + + Before: + + ``` + use Symfony\Component\Validator\Mapping\ElementMetadata; + + class MyMetadata extends ElementMetadata + { + } + ``` + + After: + + ``` + use Symfony\Component\Validator\Mapping\GenericMetadata; + + class MyMetadata extends GenericMetadata + { + } + ``` + + * The interface `ExecutionContextInterface` and its implementation + `ExecutionContext` were moved to the `Context` namespace. + + Before: + + ``` + use Symfony\Component\Validator\ExecutionContextInterface; + ``` + + After: + + ``` + use Symfony\Component\Validator\Context\ExecutionContextInterface; + ``` + + The interface now contains the following additional methods: + + * `getValidator()` + * `getObject()` + * `setNode()` + * `setGroup()` + * `markGroupAsValidated()` + * `isGroupValidated()` + * `markConstraintAsValidated()` + * `isConstraintValidated()` + + See the inline documentation of these methods for more information. + + The method `addViolationAt()` was removed. You should use `buildViolation()` + instead. + + Before: + + ``` + $context->addViolationAt('property', 'The value {{ value }} is invalid.', array( + '{{ value }}' => $invalidValue, + )); + ``` + + After: + + ``` + $context->buildViolation('The value {{ value }} is invalid.') + ->atPath('property') + ->setParameter('{{ value }}', $invalidValue) + ->addViolation(); + )); + ``` + + The methods `validate()` and `validateValue()` were removed. You should use + `getValidator()` together with `inContext()` instead. + + Before: + + ``` + $context->validate($object); + ``` + + After: + + ``` + $context->getValidator() + ->inContext($context) + ->validate($object); + ``` + + The parameters `$invalidValue`, `$plural` and `$code` were removed from + `addViolation()`. You should use `buildViolation()` instead. See above for + an example. + + The method `getMetadataFactory()` was removed. You can use `getValidator()` + instead and use the methods `getMetadataFor()` or `hasMetadataFor()` on the + validator instance. + + Before: + + ``` + $metadata = $context->getMetadataFactory()->getMetadataFor($myClass); + ``` + + After: + + ``` + $metadata = $context->getValidator()->getMetadataFor($myClass); + ``` + + * The interface `GlobalExecutionContextInterface` was removed. Most of the + information provided by that interface can be queried from + `Context\ExecutionContextInterface` instead. + + * The interface `MetadataFactoryInterface` was moved to the `Mapping\Factory` + namespace along with its implementations `BlackholeMetadataFactory` and + `ClassMetadataFactory`. These classes were furthermore renamed to + `BlackHoleMetadataFactory` and `LazyLoadingMetadataFactory`. + + Before: + + ``` + use Symfony\Component\Validator\Mapping\ClassMetadataFactory; + + $factory = new ClassMetadataFactory($loader); + ``` + + After: + + ``` + use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory; + + $factory = new LazyLoadingMetadataFactory($loader); + ``` + + * The option `$deep` was removed from the constraint `Valid`. When traversing + arrays, nested arrays are always traversed (same behavior as before). When + traversing nested objects, their traversal strategy is used. + + * The method `ValidatorBuilder::setPropertyAccessor()` was removed. The + validator now functions without a property accessor. + + * The methods `getMessageParameters()` and `getMessagePluralization()` in + `ConstraintViolation` were renamed to `getParameters()` and `getPlural()`. + + Before: + + ``` + $parameters = $violation->getMessageParameters(); + $plural = $violation->getMessagePluralization(); + ``` + + After: + + ``` + $parameters = $violation->getParameters(); + $plural = $violation->getPlural(); + ``` + ### Yaml * The ability to pass file names to `Yaml::parse()` has been removed. diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php index bf9f33811199a..6f58fc21bebf1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddValidatorInitializersPass.php @@ -19,15 +19,17 @@ class AddValidatorInitializersPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!$container->hasDefinition('validator')) { + if (!$container->hasDefinition('validator.builder')) { return; } + $validatorBuilder = $container->getDefinition('validator.builder'); + $initializers = array(); foreach ($container->findTaggedServiceIds('validator.initializer') as $id => $attributes) { $initializers[] = new Reference($id); } - $container->getDefinition('validator')->replaceArgument(4, $initializers); + $validatorBuilder->addMethodCall('addObjectInitializers', array($initializers)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index a317d3cc8efe1..c2639295636c6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -444,8 +444,25 @@ private function addValidationSection(ArrayNodeDefinition $rootNode) ->children() ->scalarNode('cache')->end() ->booleanNode('enable_annotations')->defaultFalse()->end() + ->arrayNode('static_method') + ->defaultValue(array('loadClassMetadata')) + ->prototype('scalar')->end() + ->treatFalseLike(array()) + ->validate() + ->ifTrue(function ($v) { return !is_array($v); }) + ->then(function ($v) { return (array) $v; }) + ->end() + ->end() ->scalarNode('translation_domain')->defaultValue('validators')->end() ->booleanNode('strict_email')->defaultFalse()->end() + ->enumNode('api') + ->values(array('2.4', '2.5', '2.5-bc', 'auto')) + ->defaultValue('auto') + ->beforeNormalization() + ->ifTrue(function ($v) { return is_scalar($v); }) + ->then(function ($v) { return (string) $v; }) + ->end() + ->end() ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 5ee6982bc8d1f..b2d14973e9def 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -21,6 +21,7 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Validator\Validation; /** * FrameworkExtension. @@ -674,27 +675,57 @@ private function registerValidationConfiguration(array $config, ContainerBuilder $loader->load('validator.xml'); + $validatorBuilder = $container->getDefinition('validator.builder'); + $container->setParameter('validator.translation_domain', $config['translation_domain']); - $container->setParameter('validator.mapping.loader.xml_files_loader.mapping_files', $this->getValidatorXmlMappingFiles($container)); - $container->setParameter('validator.mapping.loader.yaml_files_loader.mapping_files', $this->getValidatorYamlMappingFiles($container)); + + $xmlMappings = $this->getValidatorXmlMappingFiles($container); + $yamlMappings = $this->getValidatorYamlMappingFiles($container); + + if (count($xmlMappings) > 0) { + $validatorBuilder->addMethodCall('addXmlMappings', array($xmlMappings)); + } + + if (count($yamlMappings) > 0) { + $validatorBuilder->addMethodCall('addYamlMappings', array($yamlMappings)); + } $definition = $container->findDefinition('validator.email'); $definition->replaceArgument(0, $config['strict_email']); if (array_key_exists('enable_annotations', $config) && $config['enable_annotations']) { - $loaderChain = $container->getDefinition('validator.mapping.loader.loader_chain'); - $arguments = $loaderChain->getArguments(); - array_unshift($arguments[0], new Reference('validator.mapping.loader.annotation_loader')); - $loaderChain->setArguments($arguments); + $validatorBuilder->addMethodCall('enableAnnotationMapping', array(new Reference('annotation_reader'))); + } + + if (array_key_exists('static_method', $config) && $config['static_method']) { + foreach ($config['static_method'] as $methodName) { + $validatorBuilder->addMethodCall('addMethodMapping', array($methodName)); + } } if (isset($config['cache'])) { - $container->getDefinition('validator.mapping.class_metadata_factory') - ->replaceArgument(1, new Reference('validator.mapping.cache.'.$config['cache'])); $container->setParameter( 'validator.mapping.cache.prefix', 'validator_'.hash('sha256', $container->getParameter('kernel.root_dir')) ); + + $validatorBuilder->addMethodCall('setMetadataCache', array(new Reference('validator.mapping.cache.'.$config['cache']))); + } + + if ('auto' !== $config['api']) { + switch ($config['api']) { + case '2.4': + $api = Validation::API_VERSION_2_4; + break; + case '2.5': + $api = Validation::API_VERSION_2_5; + break; + default: + $api = Validation::API_VERSION_2_5_BC; + break; + } + + $validatorBuilder->addMethodCall('setApiVersion', array($api)); } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index fcc9e254589da..07569b54c0206 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -7,6 +7,15 @@ + + + + + + + + + @@ -151,9 +160,15 @@ + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml index 930300f75c5d1..336379a11bd66 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml @@ -5,36 +5,33 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Symfony\Component\Validator\Validator - Symfony\Component\Validator\Mapping\ClassMetadataFactory + Symfony\Component\Validator\ValidatorInterface + Symfony\Component\Validator\ValidatorBuilderInterface + Symfony\Component\Validator\Validation Symfony\Component\Validator\Mapping\Cache\ApcCache - Symfony\Component\Validator\Mapping\Loader\LoaderChain - Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader - Symfony\Component\Validator\Mapping\Loader\AnnotationLoader - Symfony\Component\Validator\Mapping\Loader\XmlFilesLoader - Symfony\Component\Validator\Mapping\Loader\YamlFilesLoader Symfony\Bundle\FrameworkBundle\Validator\ConstraintValidatorFactory - - Symfony\Component\Validator\Constraints\ExpressionValidator Symfony\Component\Validator\Constraints\EmailValidator - - - - - %validator.translation_domain% - - + -