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%
-
-
+
-
-
- null
+
+
+
+
+
+
+
+
+ %validator.translation_domain%
+
+
+
%validator.mapping.cache.prefix%
@@ -44,28 +41,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- %validator.mapping.loader.xml_files_loader.mapping_files%
-
-
-
- %validator.mapping.loader.yaml_files_loader.mapping_files%
-
-
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
index f2675d7d3018f..23208710155a0 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
@@ -126,8 +126,10 @@ protected static function getBundleDefaultConfig()
'validation' => array(
'enabled' => false,
'enable_annotations' => false,
+ 'static_method' => array('loadClassMetadata'),
'translation_domain' => 'validators',
'strict_email' => false,
+ 'api' => 'auto',
),
'annotations' => array(
'cache' => 'file',
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_4_api.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_4_api.php
new file mode 100644
index 0000000000000..7b584f67e57ef
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_2_4_api.php
@@ -0,0 +1,9 @@
+loadFromExtension('framework', array(
+ 'secret' => 's3cr3t',
+ 'validation' => array(
+ 'enabled' => true,
+ 'api' => '2.4',
+ ),
+));
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_multiple_static_methods.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_multiple_static_methods.php
new file mode 100644
index 0000000000000..c3c81b72d65a0
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_multiple_static_methods.php
@@ -0,0 +1,9 @@
+loadFromExtension('framework', array(
+ 'secret' => 's3cr3t',
+ 'validation' => array(
+ 'enabled' => true,
+ 'static_method' => array('loadFoo', 'loadBar'),
+ ),
+));
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_no_static_method.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_no_static_method.php
new file mode 100644
index 0000000000000..f309dbfc1d933
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/validation_no_static_method.php
@@ -0,0 +1,9 @@
+loadFromExtension('framework', array(
+ 'secret' => 's3cr3t',
+ 'validation' => array(
+ 'enabled' => true,
+ 'static_method' => false,
+ ),
+));
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_4_api.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_4_api.xml
new file mode 100644
index 0000000000000..247c66d8dfd08
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_2_4_api.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_multiple_static_methods.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_multiple_static_methods.xml
new file mode 100644
index 0000000000000..053f574bafe9b
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_multiple_static_methods.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ loadFoo
+ loadBar
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_no_static_method.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_no_static_method.xml
new file mode 100644
index 0000000000000..d26c7a2be50ef
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/validation_no_static_method.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_4_api.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_4_api.yml
new file mode 100644
index 0000000000000..cce573fb67e3c
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_2_4_api.yml
@@ -0,0 +1,5 @@
+framework:
+ secret: s3cr3t
+ validation:
+ enabled: true
+ api: 2.4
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_multiple_static_methods.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_multiple_static_methods.yml
new file mode 100644
index 0000000000000..6ca343351328a
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_multiple_static_methods.yml
@@ -0,0 +1,5 @@
+framework:
+ secret: s3cr3t
+ validation:
+ enabled: true
+ static_method: [loadFoo, loadBar]
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_no_static_method.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_no_static_method.yml
new file mode 100644
index 0000000000000..ca5214964259e
--- /dev/null
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/validation_no_static_method.yml
@@ -0,0 +1,5 @@
+framework:
+ secret: s3cr3t
+ validation:
+ enabled: true
+ static_method: false
diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
index d36203e3682d9..55cd188c7cfb6 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -15,6 +15,8 @@
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\Validator\Validation;
abstract class FrameworkExtensionTest extends TestCase
{
@@ -257,17 +259,25 @@ public function testValidation()
{
$container = $this->createContainerFromFile('full');
- $this->assertTrue($container->hasDefinition('validator'), '->registerValidationConfiguration() loads validator.xml');
- $this->assertTrue($container->hasDefinition('validator.mapping.loader.xml_files_loader'), '->registerValidationConfiguration() defines the XML loader');
- $this->assertTrue($container->hasDefinition('validator.mapping.loader.yaml_files_loader'), '->registerValidationConfiguration() defines the YAML loader');
-
- $xmlFiles = $container->getParameter('validator.mapping.loader.xml_files_loader.mapping_files');
$ref = new \ReflectionClass('Symfony\Component\Form\Form');
- $this->assertContains(
- realpath(dirname($ref->getFileName()).'/Resources/config/validation.xml'),
- array_map('realpath', $xmlFiles),
- '->registerValidationConfiguration() adds Form validation.xml to XML loader'
- );
+ $xmlMappings = array(realpath(dirname($ref->getFileName()).'/Resources/config/validation.xml'));
+
+ $calls = $container->getDefinition('validator.builder')->getMethodCalls();
+
+ $this->assertCount(6, $calls);
+ $this->assertSame('setConstraintValidatorFactory', $calls[0][0]);
+ $this->assertEquals(array(new Reference('validator.validator_factory')), $calls[0][1]);
+ $this->assertSame('setTranslator', $calls[1][0]);
+ $this->assertEquals(array(new Reference('translator')), $calls[1][1]);
+ $this->assertSame('setTranslationDomain', $calls[2][0]);
+ $this->assertSame(array('%validator.translation_domain%'), $calls[2][1]);
+ $this->assertSame('addXmlMappings', $calls[3][0]);
+ $this->assertSame(array($xmlMappings), $calls[3][1]);
+ $this->assertSame('addMethodMapping', $calls[4][0]);
+ $this->assertSame(array('loadClassMetadata'), $calls[4][1]);
+ $this->assertSame('setMetadataCache', $calls[5][0]);
+ $this->assertEquals(array(new Reference('validator.mapping.cache.apc')), $calls[5][1]);
+ $this->assertInstanceOf('Symfony\Component\Validator\ValidatorInterface', $container->get('validator'));
}
public function testAnnotations()
@@ -289,15 +299,14 @@ public function testValidationAnnotations()
{
$container = $this->createContainerFromFile('validation_annotations');
- $this->assertTrue($container->hasDefinition('validator.mapping.loader.annotation_loader'), '->registerValidationConfiguration() defines the annotation loader');
- $loaders = $container->getDefinition('validator.mapping.loader.loader_chain')->getArgument(0);
- $found = false;
- foreach ($loaders as $loader) {
- if ('validator.mapping.loader.annotation_loader' === (string) $loader) {
- $found = true;
- }
- }
- $this->assertTrue($found, 'validator.mapping.loader.annotation_loader is added to the loader chain.');
+ $calls = $container->getDefinition('validator.builder')->getMethodCalls();
+
+ $this->assertCount(6, $calls);
+ $this->assertSame('enableAnnotations', $calls[4][0]);
+ $this->assertEquals(array(new Reference('annotation_reader')), $calls[4][1]);
+ $this->assertSame('addMethodMapping', $calls[5][0]);
+ $this->assertSame(array('loadClassMetadata'), $calls[5][1]);
+ // no cache this time
}
public function testValidationPaths()
@@ -308,14 +317,49 @@ public function testValidationPaths()
'kernel.bundles' => array('TestBundle' => 'Symfony\Bundle\FrameworkBundle\Tests\TestBundle'),
));
- $yamlArgs = $container->getParameter('validator.mapping.loader.yaml_files_loader.mapping_files');
- $this->assertCount(1, $yamlArgs);
- $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.yml', $yamlArgs[0]);
+ $calls = $container->getDefinition('validator.builder')->getMethodCalls();
+
+ $this->assertCount(7, $calls);
+ $this->assertSame('addXmlMappings', $calls[3][0]);
+ $this->assertSame('addYamlMappings', $calls[4][0]);
+ $this->assertSame('enableAnnotations', $calls[5][0]);
+ $this->assertSame('addMethodMapping', $calls[6][0]);
+ $this->assertSame(array('loadClassMetadata'), $calls[6][1]);
+
+ $xmlMappings = $calls[3][1][0];
+ $this->assertCount(2, $xmlMappings);
+ $this->assertStringEndsWith('Component'.DIRECTORY_SEPARATOR.'Form/Resources/config/validation.xml', $xmlMappings[0]);
+ $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.xml', $xmlMappings[1]);
+
+ $yamlMappings = $calls[4][1][0];
+ $this->assertCount(1, $yamlMappings);
+ $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.yml', $yamlMappings[0]);
+ }
+
+ public function testValidationNoStaticMethod()
+ {
+ $container = $this->createContainerFromFile('validation_no_static_method');
+
+ $calls = $container->getDefinition('validator.builder')->getMethodCalls();
+
+ $this->assertCount(4, $calls);
+ $this->assertSame('addXmlMappings', $calls[3][0]);
+ // no cache, no annotations, no static methods
+ }
+
+ public function testValidationApiVersion()
+ {
+ $container = $this->createContainerFromFile('validation_2_4_api');
+
+ $calls = $container->getDefinition('validator.builder')->getMethodCalls();
- $xmlArgs = $container->getParameter('validator.mapping.loader.xml_files_loader.mapping_files');
- $this->assertCount(2, $xmlArgs);
- $this->assertStringEndsWith('Component'.DIRECTORY_SEPARATOR.'Form/Resources/config/validation.xml', $xmlArgs[0]);
- $this->assertStringEndsWith('TestBundle'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'validation.xml', $xmlArgs[1]);
+ $this->assertCount(6, $calls);
+ $this->assertSame('addXmlMappings', $calls[3][0]);
+ $this->assertSame('addMethodMapping', $calls[4][0]);
+ $this->assertSame(array('loadClassMetadata'), $calls[4][1]);
+ $this->assertSame('setApiVersion', $calls[5][0]);
+ $this->assertSame(array(Validation::API_VERSION_2_4), $calls[5][1]);
+ // no cache, no annotations
}
public function testFormsCanBeEnabledWithoutCsrfProtection()
diff --git a/src/Symfony/Component/Validator/CHANGELOG.md b/src/Symfony/Component/Validator/CHANGELOG.md
index 3eba52930f3f7..34fe232dcfe78 100644
--- a/src/Symfony/Component/Validator/CHANGELOG.md
+++ b/src/Symfony/Component/Validator/CHANGELOG.md
@@ -6,6 +6,60 @@ CHANGELOG
* deprecated `ApcCache` in favor of `DoctrineCache`
* added `DoctrineCache` to adapt any Doctrine cache
+ * `GroupSequence` now implements `ArrayAccess`, `Countable` and `Traversable`
+ * [BC BREAK] changed `ClassMetadata::getGroupSequence()` to return a `GroupSequence` instance instead of an array
+ * `Callback` can now be put onto properties (useful when you pass a closure to the constraint)
+ * deprecated `ClassBasedInterface`
+ * deprecated `MetadataInterface`
+ * deprecated `PropertyMetadataInterface`
+ * deprecated `PropertyMetadataContainerInterface`
+ * deprecated `Mapping\ElementMetadata`
+ * added `Mapping\MetadataInterface`
+ * added `Mapping\ClassMetadataInterface`
+ * added `Mapping\PropertyMetadataInterface`
+ * added `Mapping\GenericMetadata`
+ * added `Mapping\CascadingStrategy`
+ * added `Mapping\TraversalStrategy`
+ * deprecated `Mapping\ClassMetadata::accept()`
+ * deprecated `Mapping\MemberMetadata::accept()`
+ * removed array type hint of `Mapping\ClassMetadata::setGroupSequence()`
+ * deprecated `MetadataFactoryInterface`
+ * deprecated `Mapping\BlackholeMetadataFactory`
+ * deprecated `Mapping\ClassMetadataFactory`
+ * added `Mapping\Factory\MetadataFactoryInterface`
+ * added `Mapping\Factory\BlackHoleMetadataFactory`
+ * added `Mapping\Factory\LazyLoadingMetadataFactory`
+ * deprecated `ExecutionContextInterface`
+ * deprecated `ExecutionContext`
+ * deprecated `GlobalExecutionContextInterface`
+ * added `Context\ExecutionContextInterface`
+ * added `Context\ExecutionContext`
+ * added `Context\ExecutionContextFactoryInterface`
+ * added `Context\ExecutionContextFactory`
+ * deprecated `ValidatorInterface`
+ * deprecated `Validator`
+ * deprecated `ValidationVisitorInterface`
+ * deprecated `ValidationVisitor`
+ * added `Validator\ValidatorInterface`
+ * added `Validator\RecursiveValidator`
+ * added `Validator\ContextualValidatorInterface`
+ * added `Validator\RecursiveContextualValidator`
+ * added `Violation\ConstraintViolationBuilderInterface`
+ * added `Violation\ConstraintViolationBuilder`
+ * added `ConstraintViolation::getParameters()`
+ * added `ConstraintViolation::getPlural()`
+ * added `Constraints\Traverse`
+ * deprecated `$deep` property in `Constraints\Valid`
+ * added `ValidatorBuilderInterface::setApiVersion()`
+ * added `Validation::API_VERSION_2_4`
+ * added `Validation::API_VERSION_2_5`
+ * added `Exception\OutOfBoundsException`
+ * added `Exception\UnsupportedMetadataException`
+ * made `Exception\ValidatorException` extend `Exception\RuntimeException`
+ * added `Util\PropertyPath`
+ * made the PropertyAccess component an optional dependency
+ * deprecated `ValidatorBuilder::setPropertyAccessor()`
+
2.4.0
-----
diff --git a/src/Symfony/Component/Validator/ClassBasedInterface.php b/src/Symfony/Component/Validator/ClassBasedInterface.php
index c8fa25d43d796..fe532efe51702 100644
--- a/src/Symfony/Component/Validator/ClassBasedInterface.php
+++ b/src/Symfony/Component/Validator/ClassBasedInterface.php
@@ -15,6 +15,9 @@
* An object backed by a PHP class.
*
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Mapping\ClassMetadataInterface} instead.
*/
interface ClassBasedInterface
{
diff --git a/src/Symfony/Component/Validator/Constraint.php b/src/Symfony/Component/Validator/Constraint.php
index 0f2c226ae9f77..7f7f6b83c4208 100644
--- a/src/Symfony/Component/Validator/Constraint.php
+++ b/src/Symfony/Component/Validator/Constraint.php
@@ -11,9 +11,9 @@
namespace Symfony\Component\Validator;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\InvalidOptionsException;
use Symfony\Component\Validator\Exception\MissingOptionsException;
-use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* Contains the properties of a constraint definition.
diff --git a/src/Symfony/Component/Validator/ConstraintValidatorFactory.php b/src/Symfony/Component/Validator/ConstraintValidatorFactory.php
index 5cf36eccffc3a..d26680ea094c8 100644
--- a/src/Symfony/Component/Validator/ConstraintValidatorFactory.php
+++ b/src/Symfony/Component/Validator/ConstraintValidatorFactory.php
@@ -11,8 +11,6 @@
namespace Symfony\Component\Validator;
-use Symfony\Component\PropertyAccess\PropertyAccess;
-use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Validator\Constraints\ExpressionValidator;
/**
@@ -28,14 +26,11 @@ class ConstraintValidatorFactory implements ConstraintValidatorFactoryInterface
{
protected $validators = array();
- /**
- * @var PropertyAccessorInterface
- */
private $propertyAccessor;
- public function __construct(PropertyAccessorInterface $propertyAccessor = null)
+ public function __construct($propertyAccessor = null)
{
- $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
+ $this->propertyAccessor = $propertyAccessor;
}
/**
diff --git a/src/Symfony/Component/Validator/ConstraintViolation.php b/src/Symfony/Component/Validator/ConstraintViolation.php
index 36a42aaec4310..fa8d70b9543cc 100644
--- a/src/Symfony/Component/Validator/ConstraintViolation.php
+++ b/src/Symfony/Component/Validator/ConstraintViolation.php
@@ -31,12 +31,12 @@ class ConstraintViolation implements ConstraintViolationInterface
/**
* @var array
*/
- private $messageParameters;
+ private $parameters;
/**
* @var integer|null
*/
- private $messagePluralization;
+ private $plural;
/**
* @var mixed
@@ -61,27 +61,26 @@ class ConstraintViolation implements ConstraintViolationInterface
/**
* Creates a new constraint violation.
*
- * @param string $message The violation message.
- * @param string $messageTemplate The raw violation message.
- * @param array $messageParameters The parameters to substitute
- * in the raw message.
- * @param mixed $root The value originally passed
- * to the validator.
- * @param string $propertyPath The property path from the
- * root value to the invalid
- * value.
- * @param mixed $invalidValue The invalid value causing the
- * violation.
- * @param integer|null $messagePluralization The pluralization parameter.
- * @param mixed $code The error code of the
- * violation, if any.
- */
- public function __construct($message, $messageTemplate, array $messageParameters, $root, $propertyPath, $invalidValue, $messagePluralization = null, $code = null)
+ * @param string $message The violation message
+ * @param string $messageTemplate The raw violation message
+ * @param array $parameters The parameters to substitute in the
+ * raw violation message
+ * @param mixed $root The value originally passed to the
+ * validator
+ * @param string $propertyPath The property path from the root
+ * value to the invalid value
+ * @param mixed $invalidValue The invalid value that caused this
+ * violation
+ * @param integer|null $plural The number for determining the plural
+ * form when translating the message
+ * @param mixed $code The error code of the violation
+ */
+ public function __construct($message, $messageTemplate, array $parameters, $root, $propertyPath, $invalidValue, $plural = null, $code = null)
{
$this->message = $message;
$this->messageTemplate = $messageTemplate;
- $this->messageParameters = $messageParameters;
- $this->messagePluralization = $messagePluralization;
+ $this->parameters = $parameters;
+ $this->plural = $plural;
$this->root = $root;
$this->propertyPath = $propertyPath;
$this->invalidValue = $invalidValue;
@@ -130,7 +129,15 @@ public function getMessageTemplate()
*/
public function getMessageParameters()
{
- return $this->messageParameters;
+ return $this->parameters;
+ }
+
+ /**
+ * Alias of {@link getMessageParameters()}.
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
}
/**
@@ -138,7 +145,15 @@ public function getMessageParameters()
*/
public function getMessagePluralization()
{
- return $this->messagePluralization;
+ return $this->plural;
+ }
+
+ /**
+ * Alias of {@link getMessagePluralization()}.
+ */
+ public function getPlural()
+ {
+ return $this->plural;
}
/**
diff --git a/src/Symfony/Component/Validator/Constraints/Callback.php b/src/Symfony/Component/Validator/Constraints/Callback.php
index 01aeb6ddb7982..18cd7b3e92c1a 100644
--- a/src/Symfony/Component/Validator/Constraints/Callback.php
+++ b/src/Symfony/Component/Validator/Constraints/Callback.php
@@ -71,6 +71,6 @@ public function getDefaultOption()
*/
public function getTargets()
{
- return self::CLASS_CONSTRAINT;
+ return array(self::CLASS_CONSTRAINT, self::PROPERTY_CONSTRAINT);
}
}
diff --git a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php
index 39da982bb13ca..c378e55e769f7 100644
--- a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php
+++ b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php
@@ -13,8 +13,8 @@
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
-use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
/**
* Validator for Callback constraint
@@ -33,10 +33,6 @@ public function validate($object, Constraint $constraint)
if (!$constraint instanceof Callback) {
throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\Callback');
}
-
- if (null === $object) {
- return;
- }
if (null !== $constraint->callback && null !== $constraint->methods) {
throw new ConstraintDefinitionException(
@@ -60,18 +56,24 @@ public function validate($object, Constraint $constraint)
}
call_user_func($method, $object, $this->context);
- } else {
- if (!method_exists($object, $method)) {
- throw new ConstraintDefinitionException(sprintf('Method "%s" targeted by Callback constraint does not exist', $method));
- }
- $reflMethod = new \ReflectionMethod($object, $method);
+ continue;
+ }
- if ($reflMethod->isStatic()) {
- $reflMethod->invoke(null, $object, $this->context);
- } else {
- $reflMethod->invoke($object, $this->context);
- }
+ if (null === $object) {
+ continue;
+ }
+
+ if (!method_exists($object, $method)) {
+ throw new ConstraintDefinitionException(sprintf('Method "%s" targeted by Callback constraint does not exist', $method));
+ }
+
+ $reflMethod = new \ReflectionMethod($object, $method);
+
+ if ($reflMethod->isStatic()) {
+ $reflMethod->invoke(null, $object, $this->context);
+ } else {
+ $reflMethod->invoke($object, $this->context);
}
}
}
diff --git a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php
index 8323e5f6dbad6..0e8bd405a415c 100644
--- a/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php
+++ b/src/Symfony/Component/Validator/Constraints/ExpressionValidator.php
@@ -11,11 +11,13 @@
namespace Symfony\Component\Validator\Constraints;
+use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPath;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
-use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\Exception\RuntimeException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
@@ -35,8 +37,17 @@ class ExpressionValidator extends ConstraintValidator
*/
private $expressionLanguage;
- public function __construct(PropertyAccessorInterface $propertyAccessor)
+ /**
+ * @param PropertyAccessorInterface|null $propertyAccessor Optional as of Symfony 2.5
+ *
+ * @throws UnexpectedTypeException If the property accessor is invalid
+ */
+ public function __construct($propertyAccessor = null)
{
+ if (null !== $propertyAccessor && !$propertyAccessor instanceof PropertyAccessorInterface) {
+ throw new UnexpectedTypeException($propertyAccessor, 'null or \Symfony\Component\PropertyAccess\PropertyAccessorInterface');
+ }
+
$this->propertyAccessor = $propertyAccessor;
}
@@ -55,7 +66,12 @@ public function validate($value, Constraint $constraint)
$variables = array();
- if (null === $this->context->getPropertyName()) {
+ // Symfony 2.5+
+ if ($this->context instanceof ExecutionContextInterface) {
+ $variables['value'] = $value;
+ $variables['this'] = $this->context->getObject();
+ } elseif (null === $this->context->getPropertyName()) {
+ $variables['value'] = $value;
$variables['this'] = $value;
} else {
// Extract the object that the property belongs to from the object
@@ -65,7 +81,7 @@ public function validate($value, Constraint $constraint)
$root = $this->context->getRoot();
$variables['value'] = $value;
- $variables['this'] = $parentPath ? $this->propertyAccessor->getValue($root, $parentPath) : $root;
+ $variables['this'] = $parentPath ? $this->getPropertyAccessor()->getValue($root, $parentPath) : $root;
}
if (!$this->getExpressionLanguage()->evaluate($constraint->expression, $variables)) {
@@ -84,4 +100,16 @@ private function getExpressionLanguage()
return $this->expressionLanguage;
}
+
+ private function getPropertyAccessor()
+ {
+ if (null === $this->propertyAccessor) {
+ if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccess')) {
+ throw new RuntimeException('Unable to use expressions as the Symfony PropertyAccess component is not installed.');
+ }
+ $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
+ }
+
+ return $this->propertyAccessor;
+ }
}
diff --git a/src/Symfony/Component/Validator/Constraints/FileValidator.php b/src/Symfony/Component/Validator/Constraints/FileValidator.php
index 54f0b45d60dae..f789fb108418a 100644
--- a/src/Symfony/Component/Validator/Constraints/FileValidator.php
+++ b/src/Symfony/Component/Validator/Constraints/FileValidator.php
@@ -11,12 +11,12 @@
namespace Symfony\Component\Validator\Constraints;
+use Symfony\Component\HttpFoundation\File\File as FileObject;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
-use Symfony\Component\HttpFoundation\File\File as FileObject;
-use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* @author Bernhard Schussek
diff --git a/src/Symfony/Component/Validator/Constraints/GroupSequence.php b/src/Symfony/Component/Validator/Constraints/GroupSequence.php
index 304fab8c943b0..805aa1b16b56d 100644
--- a/src/Symfony/Component/Validator/Constraints/GroupSequence.php
+++ b/src/Symfony/Component/Validator/Constraints/GroupSequence.php
@@ -11,8 +11,43 @@
namespace Symfony\Component\Validator\Constraints;
+use Symfony\Component\Validator\Exception\OutOfBoundsException;
+
/**
- * Annotation for group sequences
+ * A sequence of validation groups.
+ *
+ * When validating a group sequence, each group will only be validated if all
+ * of the previous groups in the sequence succeeded. For example:
+ *
+ * $validator->validate($address, null, new GroupSequence('Basic', 'Strict'));
+ *
+ * In the first step, all constraints that belong to the group "Basic" will be
+ * validated. If none of the constraints fail, the validator will then validate
+ * the constraints in group "Strict". This is useful, for example, if "Strict"
+ * contains expensive checks that require a lot of CPU or slow, external
+ * services. You usually don't want to run expensive checks if any of the cheap
+ * checks fail.
+ *
+ * When adding metadata to a class, you can override the "Default" group of
+ * that class with a group sequence:
+ *
+ * /**
+ * * @GroupSequence({"Address", "Strict"})
+ * *\/
+ * class Address
+ * {
+ * // ...
+ * }
+ *
+ * Whenever you validate that object in the "Default" group, the group sequence
+ * will be validated:
+ *
+ * $validator->validate($address);
+ *
+ * If you want to execute the constraints of the "Default" group for a class
+ * with an overridden default group, pass the class name as group name instead:
+ *
+ * $validator->validate($address, null, "Address")
*
* @Annotation
*
@@ -20,16 +55,139 @@
*
* @api
*/
-class GroupSequence
+class GroupSequence implements \ArrayAccess, \IteratorAggregate, \Countable
{
/**
- * The members of the sequence
- * @var array
+ * The groups in the sequence
+ *
+ * @var string[]|GroupSequence[]
*/
public $groups;
+ /**
+ * The group in which cascaded objects are validated when validating
+ * this sequence.
+ *
+ * By default, cascaded objects are validated in each of the groups of
+ * the sequence.
+ *
+ * If a class has a group sequence attached, that sequence replaces the
+ * "Default" group. When validating that class in the "Default" group, the
+ * group sequence is used instead, but still the "Default" group should be
+ * cascaded to other objects.
+ *
+ * @var string|GroupSequence
+ */
+ public $cascadedGroup;
+
+ /**
+ * Creates a new group sequence.
+ *
+ * @param string[] $groups The groups in the sequence
+ */
public function __construct(array $groups)
{
- $this->groups = $groups['value'];
+ // Support for Doctrine annotations
+ $this->groups = isset($groups['value']) ? $groups['value'] : $groups;
+ }
+
+ /**
+ * Returns an iterator for this group.
+ *
+ * @return \Traversable The iterator
+ *
+ * @see \IteratorAggregate::getIterator()
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * To be removed in Symfony 3.0.
+ */
+ public function getIterator()
+ {
+ return new \ArrayIterator($this->groups);
+ }
+
+ /**
+ * Returns whether the given offset exists in the sequence.
+ *
+ * @param integer $offset The offset
+ *
+ * @return Boolean Whether the offset exists
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * To be removed in Symfony 3.0.
+ */
+ public function offsetExists($offset)
+ {
+ return isset($this->groups[$offset]);
+ }
+
+ /**
+ * Returns the group at the given offset.
+ *
+ * @param integer $offset The offset
+ *
+ * @return string The group a the given offset
+ *
+ * @throws OutOfBoundsException If the object does not exist
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * To be removed in Symfony 3.0.
+ */
+ public function offsetGet($offset)
+ {
+ if (!isset($this->groups[$offset])) {
+ throw new OutOfBoundsException(sprintf(
+ 'The offset "%s" does not exist.',
+ $offset
+ ));
+ }
+
+ return $this->groups[$offset];
+ }
+
+ /**
+ * Sets the group at the given offset.
+ *
+ * @param integer $offset The offset
+ * @param string $value The group name
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * To be removed in Symfony 3.0.
+ */
+ public function offsetSet($offset, $value)
+ {
+ if (null !== $offset) {
+ $this->groups[$offset] = $value;
+
+ return;
+ }
+
+ $this->groups[] = $value;
+ }
+
+ /**
+ * Removes the group at the given offset.
+ *
+ * @param integer $offset The offset
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * To be removed in Symfony 3.0.
+ */
+ public function offsetUnset($offset)
+ {
+ unset($this->groups[$offset]);
+ }
+
+ /**
+ * Returns the number of groups in the sequence.
+ *
+ * @return integer The number of groups
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * To be removed in Symfony 3.0.
+ */
+ public function count()
+ {
+ return count($this->groups);
}
}
diff --git a/src/Symfony/Component/Validator/Constraints/Ip.php b/src/Symfony/Component/Validator/Constraints/Ip.php
index 099f2aabd7348..0f124f9c05bff 100644
--- a/src/Symfony/Component/Validator/Constraints/Ip.php
+++ b/src/Symfony/Component/Validator/Constraints/Ip.php
@@ -11,8 +11,8 @@
namespace Symfony\Component\Validator\Constraints;
-use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
/**
* Validates that a value is a valid IP address
diff --git a/src/Symfony/Component/Validator/Constraints/Traverse.php b/src/Symfony/Component/Validator/Constraints/Traverse.php
new file mode 100644
index 0000000000000..4abae6c67ab34
--- /dev/null
+++ b/src/Symfony/Component/Validator/Constraints/Traverse.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Constraints;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+
+/**
+ * @Annotation
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+class Traverse extends Constraint
+{
+ public $traverse = true;
+
+ public function __construct($options = null)
+ {
+ if (is_array($options) && array_key_exists('groups', $options)) {
+ throw new ConstraintDefinitionException(sprintf(
+ 'The option "groups" is not supported by the constraint %s',
+ __CLASS__
+ ));
+ }
+
+ parent::__construct($options);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDefaultOption()
+ {
+ return 'traverse';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTargets()
+ {
+ return self::CLASS_CONSTRAINT;
+ }
+}
diff --git a/src/Symfony/Component/Validator/Constraints/Valid.php b/src/Symfony/Component/Validator/Constraints/Valid.php
index ab4676d3dfea7..fe50d2d24167b 100644
--- a/src/Symfony/Component/Validator/Constraints/Valid.php
+++ b/src/Symfony/Component/Validator/Constraints/Valid.php
@@ -25,12 +25,18 @@ class Valid extends Constraint
{
public $traverse = true;
- public $deep = false;
+ /**
+ * @deprecated Deprecated as of version 2.5, to be removed in Symfony 3.0.
+ */
+ public $deep = true;
public function __construct($options = null)
{
if (is_array($options) && array_key_exists('groups', $options)) {
- throw new ConstraintDefinitionException(sprintf('The option "groups" is not supported by the constraint %s', __CLASS__));
+ throw new ConstraintDefinitionException(sprintf(
+ 'The option "groups" is not supported by the constraint %s',
+ __CLASS__
+ ));
}
parent::__construct($options);
diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php
new file mode 100644
index 0000000000000..a02c587706ecd
--- /dev/null
+++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php
@@ -0,0 +1,363 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Context;
+
+use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Component\Validator\ClassBasedInterface;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\ConstraintViolationList;
+use Symfony\Component\Validator\Exception\BadMethodCallException;
+use Symfony\Component\Validator\Mapping\MetadataInterface;
+use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
+use Symfony\Component\Validator\Util\PropertyPath;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
+
+/**
+ * The context used and created by {@link ExecutionContextFactory}.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @see ExecutionContextInterface
+ *
+ * @internal You should not instantiate or use this class. Code against
+ * {@link ExecutionContextInterface} instead.
+ */
+class ExecutionContext implements ExecutionContextInterface
+{
+ /**
+ * @var ValidatorInterface
+ */
+ private $validator;
+
+ /**
+ * The root value of the validated object graph.
+ *
+ * @var mixed
+ */
+ private $root;
+
+ /**
+ * @var TranslatorInterface
+ */
+ private $translator;
+
+ /**
+ * @var string
+ */
+ private $translationDomain;
+
+ /**
+ * The violations generated in the current context.
+ *
+ * @var ConstraintViolationList
+ */
+ private $violations;
+
+ /**
+ * The currently validated value.
+ *
+ * @var mixed
+ */
+ private $value;
+
+ /**
+ * The currently validated object.
+ *
+ * @var object|null
+ */
+ private $object;
+
+ /**
+ * The property path leading to the current value.
+ *
+ * @var string
+ */
+ private $propertyPath = '';
+
+ /**
+ * The current validation metadata.
+ *
+ * @var MetadataInterface
+ */
+ private $metadata;
+
+ /**
+ * The currently validated group.
+ *
+ * @var string|null
+ */
+ private $group;
+
+ /**
+ * Stores which objects have been validated in which group.
+ *
+ * @var array
+ */
+ private $validatedObjects = array();
+
+ /**
+ * Stores which class constraint has been validated for which object.
+ *
+ * @var array
+ */
+ private $validatedConstraints = array();
+
+ /**
+ * Creates a new execution context.
+ *
+ * @param ValidatorInterface $validator The validator
+ * @param mixed $root The root value of the
+ * validated object graph
+ * @param TranslatorInterface $translator The translator
+ * @param string|null $translationDomain The translation domain to
+ * use for translating
+ * violation messages
+ *
+ * @internal Called by {@link ExecutionContextFactory}. Should not be used
+ * in user code.
+ */
+ public function __construct(ValidatorInterface $validator, $root, TranslatorInterface $translator, $translationDomain = null)
+ {
+ $this->validator = $validator;
+ $this->root = $root;
+ $this->translator = $translator;
+ $this->translationDomain = $translationDomain;
+ $this->violations = new ConstraintViolationList();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setNode($value, $object, MetadataInterface $metadata, $propertyPath)
+ {
+ $this->value = $value;
+ $this->object = $object;
+ $this->metadata = $metadata;
+ $this->propertyPath = (string) $propertyPath;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setGroup($group)
+ {
+ $this->group = $group;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addViolation($message, array $parameters = array(), $invalidValue = null, $plural = null, $code = null)
+ {
+ // The parameters $invalidValue and following are ignored by the new
+ // API, as they are not present in the new interface anymore.
+ // You should use buildViolation() instead.
+ if (func_num_args() > 2) {
+ throw new BadMethodCallException(
+ 'The parameters $invalidValue, $pluralization and $code are '.
+ 'not supported anymore as of Symfony 2.5. Please use '.
+ 'buildViolation() instead or enable the legacy mode.'
+ );
+ }
+
+ $this->violations->add(new ConstraintViolation(
+ $this->translator->trans($message, $parameters, $this->translationDomain),
+ $message,
+ $parameters,
+ $this->root,
+ $this->getPropertyPath(),
+ $this->getValue(),
+ null,
+ null
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildViolation($message, array $parameters = array())
+ {
+ return new ConstraintViolationBuilder(
+ $this->violations,
+ $message,
+ $parameters,
+ $this->root,
+ $this->getPropertyPath(),
+ $this->getValue(),
+ $this->translator,
+ $this->translationDomain
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getViolations()
+ {
+ return $this->violations;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValidator()
+ {
+ return $this->validator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getRoot()
+ {
+ return $this->root;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getObject()
+ {
+ return $this->object;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata()
+ {
+ return $this->metadata;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroup()
+ {
+ return $this->group;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getClassName()
+ {
+ return $this->metadata instanceof ClassBasedInterface ? $this->metadata->getClassName() : null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPropertyName()
+ {
+ return $this->metadata instanceof PropertyMetadataInterface ? $this->metadata->getPropertyName() : null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPropertyPath($subPath = '')
+ {
+ return PropertyPath::append($this->propertyPath, $subPath);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addViolationAt($subPath, $message, array $parameters = array(), $invalidValue = null, $plural = null, $code = null)
+ {
+ throw new BadMethodCallException(
+ 'addViolationAt() is not supported anymore as of Symfony 2.5. '.
+ 'Please use buildViolation() instead or enable the legacy mode.'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false)
+ {
+ throw new BadMethodCallException(
+ 'validate() is not supported anymore as of Symfony 2.5. '.
+ 'Please use getValidator() instead or enable the legacy mode.'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateValue($value, $constraints, $subPath = '', $groups = null)
+ {
+ throw new BadMethodCallException(
+ 'validateValue() is not supported anymore as of Symfony 2.5. '.
+ 'Please use getValidator() instead or enable the legacy mode.'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadataFactory()
+ {
+ throw new BadMethodCallException(
+ 'getMetadataFactory() is not supported anymore as of Symfony 2.5. '.
+ 'Please use getValidator() in combination with getMetadataFor() '.
+ 'or hasMetadataFor() instead or enable the legacy mode.'
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function markGroupAsValidated($cacheKey, $groupHash)
+ {
+ if (!isset($this->validatedObjects[$cacheKey])) {
+ $this->validatedObjects[$cacheKey] = array();
+ }
+
+ $this->validatedObjects[$cacheKey][$groupHash] = true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isGroupValidated($cacheKey, $groupHash)
+ {
+ return isset($this->validatedObjects[$cacheKey][$groupHash]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function markConstraintAsValidated($cacheKey, $constraintHash)
+ {
+ $this->validatedConstraints[$cacheKey.':'.$constraintHash] = true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isConstraintValidated($cacheKey, $constraintHash)
+ {
+ return isset($this->validatedConstraints[$cacheKey.':'.$constraintHash]);
+ }
+}
diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php b/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php
new file mode 100644
index 0000000000000..52bd1e6907cea
--- /dev/null
+++ b/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Context;
+
+use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+
+/**
+ * Creates new {@link ExecutionContext} instances.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @internal You should not instantiate or use this class. Code against
+ * {@link ExecutionContextFactoryInterface} instead.
+ */
+class ExecutionContextFactory implements ExecutionContextFactoryInterface
+{
+ /**
+ * @var TranslatorInterface
+ */
+ private $translator;
+
+ /**
+ * @var string|null
+ */
+ private $translationDomain;
+
+ /**
+ * Creates a new context factory.
+ *
+ * @param TranslatorInterface $translator The translator
+ * @param string|null $translationDomain The translation domain to
+ * use for translating
+ * violation messages
+ */
+ public function __construct(TranslatorInterface $translator, $translationDomain = null)
+ {
+ $this->translator = $translator;
+ $this->translationDomain = $translationDomain;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createContext(ValidatorInterface $validator, $root)
+ {
+ return new ExecutionContext(
+ $validator,
+ $root,
+ $this->translator,
+ $this->translationDomain
+ );
+ }
+}
diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextFactoryInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextFactoryInterface.php
new file mode 100644
index 0000000000000..f0ee00174f7c3
--- /dev/null
+++ b/src/Symfony/Component/Validator/Context/ExecutionContextFactoryInterface.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Context;
+
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+
+/**
+ * Creates instances of {@link ExecutionContextInterface}.
+ *
+ * You can use a custom factory if you want to customize the execution context
+ * that is passed through the validation run.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+interface ExecutionContextFactoryInterface
+{
+ /**
+ * Creates a new execution context.
+ *
+ * @param ValidatorInterface $validator The validator
+ * @param mixed $root The root value of the validated
+ * object graph
+ *
+ * @return ExecutionContextInterface The new execution context
+ */
+ public function createContext(ValidatorInterface $validator, $root);
+}
diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php
new file mode 100644
index 0000000000000..ab539fd81de42
--- /dev/null
+++ b/src/Symfony/Component/Validator/Context/ExecutionContextInterface.php
@@ -0,0 +1,189 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Context;
+
+use Symfony\Component\Validator\ExecutionContextInterface as LegacyExecutionContextInterface;
+use Symfony\Component\Validator\Mapping\MetadataInterface;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+use Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface;
+
+/**
+ * The context of a validation run.
+ *
+ * The context collects all violations generated during the validation. By
+ * default, validators execute all validations in a new context:
+ *
+ * $violations = $validator->validate($object);
+ *
+ * When you make another call to the validator, while the validation is in
+ * progress, the violations will be isolated from each other:
+ *
+ * public function validate($value, Constraint $constraint)
+ * {
+ * $validator = $this->context->getValidator();
+ *
+ * // The violations are not added to $this->context
+ * $violations = $validator->validate($value);
+ * }
+ *
+ * However, if you want to add the violations to the current context, use the
+ * {@link ValidatorInterface::inContext()} method:
+ *
+ * public function validate($value, Constraint $constraint)
+ * {
+ * $validator = $this->context->getValidator();
+ *
+ * // The violations are added to $this->context
+ * $validator
+ * ->inContext($this->context)
+ * ->validate($value)
+ * ;
+ * }
+ *
+ * Additionally, the context provides information about the current state of
+ * the validator, such as the currently validated class, the name of the
+ * currently validated property and more. These values change over time, so you
+ * cannot store a context and expect that the methods still return the same
+ * results later on.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+interface ExecutionContextInterface extends LegacyExecutionContextInterface
+{
+ /**
+ * Returns a builder for adding a violation with extended information.
+ *
+ * Call {@link ConstraintViolationBuilderInterface::addViolation()} to
+ * add the violation when you're done with the configuration:
+ *
+ * $context->buildViolation('Please enter a number between %min% and %max.')
+ * ->setParameter('%min%', 3)
+ * ->setParameter('%max%', 10)
+ * ->setTranslationDomain('number_validation')
+ * ->addViolation();
+ *
+ * @param string $message The error message
+ * @param array $parameters The parameters substituted in the error message
+ *
+ * @return ConstraintViolationBuilderInterface The violation builder
+ */
+ public function buildViolation($message, array $parameters = array());
+
+ /**
+ * Returns the validator.
+ *
+ * Useful if you want to validate additional constraints:
+ *
+ * public function validate($value, Constraint $constraint)
+ * {
+ * $validator = $this->context->getValidator();
+ *
+ * $violations = $validator->validateValue($value, new Length(array('min' => 3)));
+ *
+ * if (count($violations) > 0) {
+ * // ...
+ * }
+ * }
+ *
+ * @return ValidatorInterface
+ */
+ public function getValidator();
+
+ /**
+ * Returns the currently validated object.
+ *
+ * If the validator is currently validating a class constraint, the
+ * object of that class is returned. If it is a validating a property or
+ * getter constraint, the object that the property/getter belongs to is
+ * returned.
+ *
+ * In other cases, null is returned.
+ *
+ * @return object|null The currently validated object or null.
+ */
+ public function getObject();
+
+ /**
+ * Sets the currently validated value.
+ *
+ * @param mixed $value The validated value
+ * @param object|null $object The currently validated object
+ * @param MetadataInterface $metadata The validation metadata
+ * @param string $propertyPath The property path to the current value
+ *
+ * @internal Used by the validator engine. Should not be called by user
+ * code.
+ */
+ public function setNode($value, $object, MetadataInterface $metadata, $propertyPath);
+
+ /**
+ * Sets the currently validated group.
+ *
+ * @param string|null $group The validated group
+ *
+ * @internal Used by the validator engine. Should not be called by user
+ * code.
+ */
+ public function setGroup($group);
+
+ /**
+ * Marks an object as validated in a specific validation group.
+ *
+ * @param string $cacheKey The hash of the object
+ * @param string $groupHash The group's name or hash, if it is group
+ * sequence
+ *
+ * @internal Used by the validator engine. Should not be called by user
+ * code.
+ */
+ public function markGroupAsValidated($cacheKey, $groupHash);
+
+ /**
+ * Returns whether an object was validated in a specific validation group.
+ *
+ * @param string $cacheKey The hash of the object
+ * @param string $groupHash The group's name or hash, if it is group
+ * sequence
+ *
+ * @return Boolean Whether the object was already validated for that
+ * group
+ *
+ * @internal Used by the validator engine. Should not be called by user
+ * code.
+ */
+ public function isGroupValidated($cacheKey, $groupHash);
+
+ /**
+ * Marks a constraint as validated for an object.
+ *
+ * @param string $cacheKey The hash of the object
+ * @param string $constraintHash The hash of the constraint
+ *
+ * @internal Used by the validator engine. Should not be called by user
+ * code.
+ */
+ public function markConstraintAsValidated($cacheKey, $constraintHash);
+
+ /**
+ * Returns whether a constraint was validated for an object.
+ *
+ * @param string $cacheKey The hash of the object
+ * @param string $constraintHash The hash of the constraint
+ *
+ * @return Boolean Whether the constraint was already validated
+ *
+ * @internal Used by the validator engine. Should not be called by user
+ * code.
+ */
+ public function isConstraintValidated($cacheKey, $constraintHash);
+}
diff --git a/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php b/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php
new file mode 100644
index 0000000000000..de34b1fc2cae6
--- /dev/null
+++ b/src/Symfony/Component/Validator/Context/LegacyExecutionContext.php
@@ -0,0 +1,156 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Context;
+
+use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Component\Validator\Constraints\Valid;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+
+/**
+ * An execution context that is compatible with the legacy API (< 2.5).
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * To be removed in Symfony 3.0.
+ */
+class LegacyExecutionContext extends ExecutionContext
+{
+ /**
+ * @var MetadataFactoryInterface
+ */
+ private $metadataFactory;
+
+ /**
+ * Creates a new context.
+ *
+ * @see ExecutionContext::__construct()
+ *
+ * @internal Called by {@link LegacyExecutionContextFactory}. Should not be used
+ * in user code.
+ */
+ public function __construct(ValidatorInterface $validator, $root, MetadataFactoryInterface $metadataFactory, TranslatorInterface $translator, $translationDomain = null)
+ {
+ parent::__construct(
+ $validator,
+ $root,
+ $translator,
+ $translationDomain
+ );
+
+ $this->metadataFactory = $metadataFactory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addViolation($message, array $parameters = array(), $invalidValue = null, $plural = null, $code = null)
+ {
+ if (func_num_args() > 2) {
+ $this
+ ->buildViolation($message, $parameters)
+ ->setInvalidValue($invalidValue)
+ ->setPlural($plural)
+ ->setCode($code)
+ ->addViolation()
+ ;
+
+ return;
+ }
+
+ parent::addViolation($message, $parameters);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addViolationAt($subPath, $message, array $parameters = array(), $invalidValue = null, $plural = null, $code = null)
+ {
+ if (func_num_args() > 2) {
+ $this
+ ->buildViolation($message, $parameters)
+ ->atPath($subPath)
+ ->setInvalidValue($invalidValue)
+ ->setPlural($plural)
+ ->setCode($code)
+ ->addViolation()
+ ;
+
+ return;
+ }
+
+ $this
+ ->buildViolation($message, $parameters)
+ ->atPath($subPath)
+ ->addViolation()
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false)
+ {
+ if (is_array($value)) {
+ // The $traverse flag is ignored for arrays
+ $constraint = new Valid(array('traverse' => true, 'deep' => $deep));
+
+ return $this
+ ->getValidator()
+ ->inContext($this)
+ ->atPath($subPath)
+ ->validate($value, $constraint, $groups)
+ ;
+ }
+
+ if ($traverse && $value instanceof \Traversable) {
+ $constraint = new Valid(array('traverse' => true, 'deep' => $deep));
+
+ return $this
+ ->getValidator()
+ ->inContext($this)
+ ->atPath($subPath)
+ ->validate($value, $constraint, $groups)
+ ;
+ }
+
+ return $this
+ ->getValidator()
+ ->inContext($this)
+ ->atPath($subPath)
+ ->validate($value, null, $groups)
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateValue($value, $constraints, $subPath = '', $groups = null)
+ {
+ return $this
+ ->getValidator()
+ ->inContext($this)
+ ->atPath($subPath)
+ ->validate($value, $constraints, $groups)
+ ;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadataFactory()
+ {
+ return $this->metadataFactory;
+ }
+}
diff --git a/src/Symfony/Component/Validator/Context/LegacyExecutionContextFactory.php b/src/Symfony/Component/Validator/Context/LegacyExecutionContextFactory.php
new file mode 100644
index 0000000000000..cf5cd07e9ef54
--- /dev/null
+++ b/src/Symfony/Component/Validator/Context/LegacyExecutionContextFactory.php
@@ -0,0 +1,73 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Context;
+
+use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+
+/**
+ * Creates new {@link LegacyExecutionContext} instances.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * To be removed in Symfony 3.0.
+ */
+class LegacyExecutionContextFactory implements ExecutionContextFactoryInterface
+{
+ /**
+ * @var MetadataFactoryInterface
+ */
+ private $metadataFactory;
+
+ /**
+ * @var TranslatorInterface
+ */
+ private $translator;
+
+ /**
+ * @var string|null
+ */
+ private $translationDomain;
+
+ /**
+ * Creates a new context factory.
+ *
+ * @param MetadataFactoryInterface $metadataFactory The metadata factory
+ * @param TranslatorInterface $translator The translator
+ * @param string|null $translationDomain The translation domain
+ * to use for translating
+ * violation messages
+ */
+ public function __construct(MetadataFactoryInterface $metadataFactory, TranslatorInterface $translator, $translationDomain = null)
+ {
+ $this->metadataFactory = $metadataFactory;
+ $this->translator = $translator;
+ $this->translationDomain = $translationDomain;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createContext(ValidatorInterface $validator, $root)
+ {
+ return new LegacyExecutionContext(
+ $validator,
+ $root,
+ $this->metadataFactory,
+ $this->translator,
+ $this->translationDomain
+ );
+ }
+}
diff --git a/src/Symfony/Component/Validator/DefaultTranslator.php b/src/Symfony/Component/Validator/DefaultTranslator.php
index 20b2e11350839..3340cce6330b9 100644
--- a/src/Symfony/Component/Validator/DefaultTranslator.php
+++ b/src/Symfony/Component/Validator/DefaultTranslator.php
@@ -11,9 +11,9 @@
namespace Symfony\Component\Validator;
+use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Exception\BadMethodCallException;
use Symfony\Component\Validator\Exception\InvalidArgumentException;
-use Symfony\Component\Translation\TranslatorInterface;
/**
* Simple translator implementation that simply replaces the parameters in
diff --git a/src/Symfony/Component/Validator/Exception/OutOfBoundsException.php b/src/Symfony/Component/Validator/Exception/OutOfBoundsException.php
new file mode 100644
index 0000000000000..30906e8a82ca0
--- /dev/null
+++ b/src/Symfony/Component/Validator/Exception/OutOfBoundsException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Exception;
+
+/**
+ * Base OutOfBoundsException for the Validator component.
+ *
+ * @author Bernhard Schussek
+ */
+class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface
+{
+}
diff --git a/src/Symfony/Component/Validator/Exception/UnsupportedMetadataException.php b/src/Symfony/Component/Validator/Exception/UnsupportedMetadataException.php
new file mode 100644
index 0000000000000..c6ece50b70062
--- /dev/null
+++ b/src/Symfony/Component/Validator/Exception/UnsupportedMetadataException.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Exception;
+
+/**
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+class UnsupportedMetadataException extends InvalidArgumentException
+{
+}
diff --git a/src/Symfony/Component/Validator/Exception/ValidatorException.php b/src/Symfony/Component/Validator/Exception/ValidatorException.php
index 6ee2416d84825..28bd4704e8fdb 100644
--- a/src/Symfony/Component/Validator/Exception/ValidatorException.php
+++ b/src/Symfony/Component/Validator/Exception/ValidatorException.php
@@ -11,6 +11,6 @@
namespace Symfony\Component\Validator\Exception;
-class ValidatorException extends \RuntimeException
+class ValidatorException extends RuntimeException
{
}
diff --git a/src/Symfony/Component/Validator/ExecutionContext.php b/src/Symfony/Component/Validator/ExecutionContext.php
index 31a959187e354..5407744bb7093 100644
--- a/src/Symfony/Component/Validator/ExecutionContext.php
+++ b/src/Symfony/Component/Validator/ExecutionContext.php
@@ -20,6 +20,9 @@
*
* @author Fabien Potencier
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Context\ExecutionContext} instead.
*/
class ExecutionContext implements ExecutionContextInterface
{
@@ -87,13 +90,13 @@ public function __construct(GlobalExecutionContextInterface $globalContext, Tran
/**
* {@inheritdoc}
*/
- public function addViolation($message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
+ public function addViolation($message, array $params = array(), $invalidValue = null, $plural = null, $code = null)
{
- if (null === $pluralization) {
+ if (null === $plural) {
$translatedMessage = $this->translator->trans($message, $params, $this->translationDomain);
} else {
try {
- $translatedMessage = $this->translator->transChoice($message, $pluralization, $params, $this->translationDomain);
+ $translatedMessage = $this->translator->transChoice($message, $plural, $params, $this->translationDomain);
} catch (\InvalidArgumentException $e) {
$translatedMessage = $this->translator->trans($message, $params, $this->translationDomain);
}
@@ -107,7 +110,7 @@ public function addViolation($message, array $params = array(), $invalidValue =
$this->propertyPath,
// check using func_num_args() to allow passing null values
func_num_args() >= 3 ? $invalidValue : $this->value,
- $pluralization,
+ $plural,
$code
));
}
@@ -115,19 +118,19 @@ public function addViolation($message, array $params = array(), $invalidValue =
/**
* {@inheritdoc}
*/
- public function addViolationAt($subPath, $message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null)
+ public function addViolationAt($subPath, $message, array $parameters = array(), $invalidValue = null, $plural = null, $code = null)
{
$this->globalContext->getViolations()->add(new ConstraintViolation(
- null === $pluralization
- ? $this->translator->trans($message, $params, $this->translationDomain)
- : $this->translator->transChoice($message, $pluralization, $params, $this->translationDomain),
+ null === $plural
+ ? $this->translator->trans($message, $parameters, $this->translationDomain)
+ : $this->translator->transChoice($message, $plural, $parameters, $this->translationDomain),
$message,
- $params,
+ $parameters,
$this->globalContext->getRoot(),
$this->getPropertyPath($subPath),
// check using func_num_args() to allow passing null values
func_num_args() >= 4 ? $invalidValue : $this->value,
- $pluralization,
+ $plural,
$code
));
}
diff --git a/src/Symfony/Component/Validator/ExecutionContextInterface.php b/src/Symfony/Component/Validator/ExecutionContextInterface.php
index 0b6c86633d6e5..3705bc43102ac 100644
--- a/src/Symfony/Component/Validator/ExecutionContextInterface.php
+++ b/src/Symfony/Component/Validator/ExecutionContextInterface.php
@@ -82,36 +82,47 @@
* @author Bernhard Schussek
*
* @api
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Context\ExecutionContextInterface} instead.
*/
interface ExecutionContextInterface
{
/**
* Adds a violation at the current node of the validation graph.
*
- * @param string $message The error message.
- * @param array $params The parameters substituted in the error message.
- * @param mixed $invalidValue The invalid, validated value.
- * @param integer|null $pluralization The number to use to pluralize of the message.
- * @param integer|null $code The violation code.
+ * @param string $message The error message
+ * @param array $params The parameters substituted in the error message
+ * @param mixed $invalidValue The invalid, validated value
+ * @param integer|null $plural The number to use to pluralize of the message
+ * @param integer|null $code The violation code
*
* @api
+ *
+ * @deprecated The parameters $invalidValue, $pluralization and $code are
+ * deprecated since version 2.5 and will be removed in
+ * Symfony 3.0.
*/
- public function addViolation($message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null);
+ public function addViolation($message, array $params = array(), $invalidValue = null, $plural = null, $code = null);
/**
* Adds a violation at the validation graph node with the given property
* path relative to the current property path.
*
- * @param string $subPath The relative property path for the violation.
- * @param string $message The error message.
- * @param array $params The parameters substituted in the error message.
- * @param mixed $invalidValue The invalid, validated value.
- * @param integer|null $pluralization The number to use to pluralize of the message.
- * @param integer|null $code The violation code.
+ * @param string $subPath The relative property path for the violation
+ * @param string $message The error message
+ * @param array $parameters The parameters substituted in the error message
+ * @param mixed $invalidValue The invalid, validated value
+ * @param integer|null $plural The number to use to pluralize of the message
+ * @param integer|null $code The violation code
*
* @api
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Context\ExecutionContextInterface::buildViolation()}
+ * instead.
*/
- public function addViolationAt($subPath, $message, array $params = array(), $invalidValue = null, $pluralization = null, $code = null);
+ public function addViolationAt($subPath, $message, array $parameters = array(), $invalidValue = null, $plural = null, $code = null);
/**
* Validates the given value within the scope of the current validation.
@@ -151,6 +162,10 @@ public function addViolationAt($subPath, $message, array $params = array(), $inv
* or an instance of \Traversable.
* @param Boolean $deep Whether to traverse the value recursively if
* it is a collection of collections.
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Context\ExecutionContextInterface::getValidator()}
+ * instead.
*/
public function validate($value, $subPath = '', $groups = null, $traverse = false, $deep = false);
@@ -180,6 +195,10 @@ public function validate($value, $subPath = '', $groups = null, $traverse = fals
* @param null|string|string[] $groups The groups to validate in. If you don't pass any
* groups here, the current group of the context
* will be used.
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Context\ExecutionContextInterface::getValidator()}
+ * instead.
*/
public function validateValue($value, $constraints, $subPath = '', $groups = null);
@@ -237,6 +256,12 @@ public function getMetadata();
* Returns the used metadata factory.
*
* @return MetadataFactoryInterface The metadata factory.
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Context\ExecutionContextInterface::getValidator()}
+ * instead and call
+ * {@link Validator\ValidatorInterface::getMetadataFor()} or
+ * {@link Validator\ValidatorInterface::hasMetadataFor()} there.
*/
public function getMetadataFactory();
diff --git a/src/Symfony/Component/Validator/GlobalExecutionContextInterface.php b/src/Symfony/Component/Validator/GlobalExecutionContextInterface.php
index aff44b350769f..fb2aef3bd772d 100644
--- a/src/Symfony/Component/Validator/GlobalExecutionContextInterface.php
+++ b/src/Symfony/Component/Validator/GlobalExecutionContextInterface.php
@@ -26,6 +26,9 @@
*
*
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Context\ExecutionContextInterface} instead.
*/
interface GlobalExecutionContextInterface
{
diff --git a/src/Symfony/Component/Validator/Mapping/BlackholeMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/BlackholeMetadataFactory.php
index 90dd282e08712..7913e15625499 100644
--- a/src/Symfony/Component/Validator/Mapping/BlackholeMetadataFactory.php
+++ b/src/Symfony/Component/Validator/Mapping/BlackholeMetadataFactory.php
@@ -11,28 +11,14 @@
namespace Symfony\Component\Validator\Mapping;
-use Symfony\Component\Validator\MetadataFactoryInterface;
-
/**
- * Simple implementation of MetadataFactoryInterface that can be used when using ValidatorInterface::validateValue().
+ * Alias of {@link Factory\BlackHoleMetadataFactory}.
*
* @author Fabien Potencier
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Factory\BlackHoleMetadataFactory} instead.
*/
-class BlackholeMetadataFactory implements MetadataFactoryInterface
+class BlackholeMetadataFactory extends \Symfony\Component\Validator\Mapping\Factory\BlackHoleMetadataFactory
{
- /**
- * @inheritdoc
- */
- public function getMetadataFor($value)
- {
- throw new \LogicException('BlackholeClassMetadataFactory only works with ValidatorInterface::validateValue().');
- }
-
- /**
- * @inheritdoc
- */
- public function hasMetadataFor($value)
- {
- return false;
- }
}
diff --git a/src/Symfony/Component/Validator/Mapping/Cache/DoctrineCache.php b/src/Symfony/Component/Validator/Mapping/Cache/DoctrineCache.php
index 56ead5d0ccc05..6dd5447fedc88 100644
--- a/src/Symfony/Component/Validator/Mapping/Cache/DoctrineCache.php
+++ b/src/Symfony/Component/Validator/Mapping/Cache/DoctrineCache.php
@@ -11,8 +11,8 @@
namespace Symfony\Component\Validator\Mapping\Cache;
-use Symfony\Component\Validator\Mapping\ClassMetadata;
use Doctrine\Common\Cache\Cache;
+use Symfony\Component\Validator\Mapping\ClassMetadata;
/**
* Adapts a Doctrine cache to a CacheInterface.
diff --git a/src/Symfony/Component/Validator/Mapping/CascadingStrategy.php b/src/Symfony/Component/Validator/Mapping/CascadingStrategy.php
new file mode 100644
index 0000000000000..ff2853f4e0415
--- /dev/null
+++ b/src/Symfony/Component/Validator/Mapping/CascadingStrategy.php
@@ -0,0 +1,53 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Mapping;
+
+/**
+ * Specifies whether an object should be cascaded.
+ *
+ * Cascading is relevant for any node type but class nodes. If such a node
+ * contains an object of value, and if cascading is enabled, then the node
+ * traverser will try to find class metadata for that object and validate the
+ * object against that metadata.
+ *
+ * If no metadata is found for a cascaded object, and if that object implements
+ * {@link \Traversable}, the node traverser will iterate over the object and
+ * cascade each object or collection contained within, unless iteration is
+ * prohibited by the specified {@link TraversalStrategy}.
+ *
+ * Although the constants currently represent a boolean switch, they are
+ * implemented as bit mask in order to allow future extensions.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @see TraversalStrategy
+ */
+class CascadingStrategy
+{
+ /**
+ * Specifies that a node should not be cascaded.
+ */
+ const NONE = 1;
+
+ /**
+ * Specifies that a node should be cascaded.
+ */
+ const CASCADE = 2;
+
+ /**
+ * Not instantiable.
+ */
+ private function __construct()
+ {
+ }
+}
diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php
index b7e003ec3da40..28bf5cda678bf 100644
--- a/src/Symfony/Component/Validator/Mapping/ClassMetadata.php
+++ b/src/Symfony/Component/Validator/Mapping/ClassMetadata.php
@@ -11,57 +11,102 @@
namespace Symfony\Component\Validator\Mapping;
-use Symfony\Component\Validator\ValidationVisitorInterface;
-use Symfony\Component\Validator\PropertyMetadataContainerInterface;
-use Symfony\Component\Validator\ClassBasedInterface;
-use Symfony\Component\Validator\MetadataInterface;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\GroupSequence;
+use Symfony\Component\Validator\Constraints\Traverse;
+use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
use Symfony\Component\Validator\Exception\GroupDefinitionException;
+use Symfony\Component\Validator\MetadataInterface as LegacyMetadataInterface;
+use Symfony\Component\Validator\PropertyMetadataContainerInterface;
+use Symfony\Component\Validator\ValidationVisitorInterface;
/**
- * Represents all the configured constraints on a given class.
+ * Default implementation of {@link ClassMetadataInterface}.
+ *
+ * This class supports serialization and cloning.
*
* @author Bernhard Schussek
* @author Fabien Potencier
*/
-class ClassMetadata extends ElementMetadata implements MetadataInterface, ClassBasedInterface, PropertyMetadataContainerInterface
+class ClassMetadata extends ElementMetadata implements LegacyMetadataInterface, PropertyMetadataContainerInterface, ClassMetadataInterface
{
/**
* @var string
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getClassName()} instead.
*/
public $name;
/**
* @var string
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getDefaultGroup()} instead.
*/
public $defaultGroup;
/**
* @var MemberMetadata[]
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getPropertyMetadata()} instead.
*/
public $members = array();
/**
* @var PropertyMetadata[]
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getPropertyMetadata()} instead.
*/
public $properties = array();
/**
* @var GetterMetadata[]
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getPropertyMetadata()} instead.
*/
public $getters = array();
/**
* @var array
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getGroupSequence()} instead.
*/
public $groupSequence = array();
/**
* @var Boolean
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link isGroupSequenceProvider()} instead.
*/
public $groupSequenceProvider = false;
+ /**
+ * The strategy for traversing traversable objects.
+ *
+ * By default, only instances of {@link \Traversable} are traversed.
+ *
+ * @var integer
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getTraversalStrategy()} instead.
+ */
+ public $traversalStrategy = TraversalStrategy::IMPLICIT;
+
/**
* @var \ReflectionClass
*/
@@ -83,6 +128,11 @@ public function __construct($class)
}
}
+ /**
+ * {@inheritdoc}
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ */
public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath, $propagatedGroup = null)
{
if (null === $propagatedGroup && Constraint::DEFAULT_GROUP === $group
@@ -118,13 +168,16 @@ public function accept(ValidationVisitorInterface $visitor, $value, $group, $pro
}
/**
- * Returns the properties to be serialized
- *
- * @return array
+ * {@inheritdoc}
*/
public function __sleep()
{
- return array_merge(parent::__sleep(), array(
+ $parentProperties = parent::__sleep();
+
+ // Don't store the cascading strategy. Classes never cascade.
+ unset($parentProperties[array_search('cascadingStrategy', $parentProperties)]);
+
+ return array_merge($parentProperties, array(
'getters',
'groupSequence',
'groupSequenceProvider',
@@ -136,9 +189,7 @@ public function __sleep()
}
/**
- * Returns the fully qualified name of the class
- *
- * @return string The fully qualified class name
+ * {@inheritdoc}
*/
public function getClassName()
{
@@ -172,14 +223,36 @@ public function addConstraint(Constraint $constraint)
{
if (!in_array(Constraint::CLASS_CONSTRAINT, (array) $constraint->getTargets())) {
throw new ConstraintDefinitionException(sprintf(
- 'The constraint %s cannot be put on classes',
+ 'The constraint "%s" cannot be put on classes.',
+ get_class($constraint)
+ ));
+ }
+
+ if ($constraint instanceof Valid) {
+ throw new ConstraintDefinitionException(sprintf(
+ 'The constraint "%s" cannot be put on classes.',
get_class($constraint)
));
}
+ if ($constraint instanceof Traverse) {
+ if ($constraint->traverse) {
+ // If traverse is true, traversal should be explicitly enabled
+ $this->traversalStrategy = TraversalStrategy::TRAVERSE;
+ } else {
+ // If traverse is false, traversal should be explicitly disabled
+ $this->traversalStrategy = TraversalStrategy::NONE;
+ }
+
+ // The constraint is not added
+ return $this;
+ }
+
$constraint->addImplicitGroupName($this->getDefaultGroup());
parent::addConstraint($constraint);
+
+ return $this;
}
/**
@@ -318,9 +391,7 @@ public function getPropertyMetadata($property)
}
/**
- * Returns all properties for which constraints are defined.
- *
- * @return array An array of property names
+ * {@inheritdoc}
*/
public function getConstrainedProperties()
{
@@ -330,35 +401,37 @@ public function getConstrainedProperties()
/**
* Sets the default group sequence for this class.
*
- * @param array $groups An array of group names
+ * @param array $groupSequence An array of group names
*
* @return ClassMetadata
*
* @throws GroupDefinitionException
*/
- public function setGroupSequence(array $groups)
+ public function setGroupSequence($groupSequence)
{
if ($this->isGroupSequenceProvider()) {
throw new GroupDefinitionException('Defining a static group sequence is not allowed with a group sequence provider');
}
- if (in_array(Constraint::DEFAULT_GROUP, $groups, true)) {
+ if (is_array($groupSequence)) {
+ $groupSequence = new GroupSequence($groupSequence);
+ }
+
+ if (in_array(Constraint::DEFAULT_GROUP, $groupSequence->groups, true)) {
throw new GroupDefinitionException(sprintf('The group "%s" is not allowed in group sequences', Constraint::DEFAULT_GROUP));
}
- if (!in_array($this->getDefaultGroup(), $groups, true)) {
+ if (!in_array($this->getDefaultGroup(), $groupSequence->groups, true)) {
throw new GroupDefinitionException(sprintf('The group "%s" is missing in the group sequence', $this->getDefaultGroup()));
}
- $this->groupSequence = $groups;
+ $this->groupSequence = $groupSequence;
return $this;
}
/**
- * Returns whether this class has an overridden default group sequence.
- *
- * @return Boolean
+ * {@inheritdoc}
*/
public function hasGroupSequence()
{
@@ -366,9 +439,7 @@ public function hasGroupSequence()
}
/**
- * Returns the default group sequence for this class.
- *
- * @return array An array of group names
+ * {@inheritdoc}
*/
public function getGroupSequence()
{
@@ -410,12 +481,20 @@ public function setGroupSequenceProvider($active)
}
/**
- * Returns whether the class is a group sequence provider.
- *
- * @return Boolean
+ * {@inheritdoc}
*/
public function isGroupSequenceProvider()
{
return $this->groupSequenceProvider;
}
+
+ /**
+ * Class nodes are never cascaded.
+ *
+ * @return Boolean Always returns false.
+ */
+ public function getCascadingStrategy()
+ {
+ return CascadingStrategy::NONE;
+ }
}
diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php
index 77eb8b528f745..9b05edde8f3cc 100644
--- a/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php
+++ b/src/Symfony/Component/Validator/Mapping/ClassMetadataFactory.php
@@ -11,102 +11,16 @@
namespace Symfony\Component\Validator\Mapping;
-use Symfony\Component\Validator\MetadataFactoryInterface;
-use Symfony\Component\Validator\Exception\NoSuchMetadataException;
-use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
-use Symfony\Component\Validator\Mapping\Cache\CacheInterface;
+use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
/**
- * A factory for creating metadata for PHP classes.
+ * Alias of {@link LazyLoadingMetadataFactory}.
*
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link LazyLoadingMetadataFactory} instead.
*/
-class ClassMetadataFactory implements MetadataFactoryInterface
+class ClassMetadataFactory extends LazyLoadingMetadataFactory
{
- /**
- * The loader for loading the class metadata
- * @var LoaderInterface
- */
- protected $loader;
-
- /**
- * The cache for caching class metadata
- * @var CacheInterface
- */
- protected $cache;
-
- protected $loadedClasses = array();
-
- public function __construct(LoaderInterface $loader = null, CacheInterface $cache = null)
- {
- $this->loader = $loader;
- $this->cache = $cache;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getMetadataFor($value)
- {
- if (!is_object($value) && !is_string($value)) {
- throw new NoSuchMetadataException(sprintf('Cannot create metadata for non-objects. Got: %s', gettype($value)));
- }
-
- $class = ltrim(is_object($value) ? get_class($value) : $value, '\\');
-
- if (isset($this->loadedClasses[$class])) {
- return $this->loadedClasses[$class];
- }
-
- if (null !== $this->cache && false !== ($this->loadedClasses[$class] = $this->cache->read($class))) {
- return $this->loadedClasses[$class];
- }
-
- if (!class_exists($class) && !interface_exists($class)) {
- throw new NoSuchMetadataException(sprintf('The class or interface "%s" does not exist.', $class));
- }
-
- $metadata = new ClassMetadata($class);
-
- // Include constraints from the parent class
- if ($parent = $metadata->getReflectionClass()->getParentClass()) {
- $metadata->mergeConstraints($this->getMetadataFor($parent->name));
- }
-
- // Include constraints from all implemented interfaces
- foreach ($metadata->getReflectionClass()->getInterfaces() as $interface) {
- if ('Symfony\Component\Validator\GroupSequenceProviderInterface' === $interface->name) {
- continue;
- }
- $metadata->mergeConstraints($this->getMetadataFor($interface->name));
- }
-
- if (null !== $this->loader) {
- $this->loader->loadClassMetadata($metadata);
- }
-
- if (null !== $this->cache) {
- $this->cache->write($metadata);
- }
-
- return $this->loadedClasses[$class] = $metadata;
- }
-
- /**
- * {@inheritdoc}
- */
- public function hasMetadataFor($value)
- {
- if (!is_object($value) && !is_string($value)) {
- return false;
- }
-
- $class = ltrim(is_object($value) ? get_class($value) : $value, '\\');
-
- if (class_exists($class) || interface_exists($class)) {
- return true;
- }
-
- return false;
- }
}
diff --git a/src/Symfony/Component/Validator/Mapping/ClassMetadataInterface.php b/src/Symfony/Component/Validator/Mapping/ClassMetadataInterface.php
new file mode 100644
index 0000000000000..332f5fa1c86cb
--- /dev/null
+++ b/src/Symfony/Component/Validator/Mapping/ClassMetadataInterface.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Mapping;
+
+use Symfony\Component\Validator\ClassBasedInterface;
+use Symfony\Component\Validator\PropertyMetadataContainerInterface as LegacyPropertyMetadataContainerInterface;
+
+/**
+ * Stores all metadata needed for validating objects of specific class.
+ *
+ * Most importantly, the metadata stores the constraints against which an object
+ * and its properties should be validated.
+ *
+ * Additionally, the metadata stores whether the "Default" group is overridden
+ * by a group sequence for that class and whether instances of that class
+ * should be traversed or not.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @see MetadataInterface
+ * @see \Symfony\Component\Validator\Constraints\GroupSequence
+ * @see \Symfony\Component\Validator\GroupSequenceProviderInterface
+ * @see TraversalStrategy
+ */
+interface ClassMetadataInterface extends MetadataInterface, LegacyPropertyMetadataContainerInterface, ClassBasedInterface
+{
+ /**
+ * Returns the names of all constrained properties.
+ *
+ * @return string[] A list of property names
+ */
+ public function getConstrainedProperties();
+
+ /**
+ * Returns whether the "Default" group is overridden by a group sequence.
+ *
+ * If it is, you can access the group sequence with {@link getGroupSequence()}.
+ *
+ * @return Boolean Returns true if the "Default" group is overridden
+ *
+ * @see \Symfony\Component\Validator\Constraints\GroupSequence
+ */
+ public function hasGroupSequence();
+
+ /**
+ * Returns the group sequence that overrides the "Default" group for this
+ * class.
+ *
+ * @return \Symfony\Component\Validator\Constraints\GroupSequence|null The group sequence or null
+ *
+ * @see \Symfony\Component\Validator\Constraints\GroupSequence
+ */
+ public function getGroupSequence();
+
+ /**
+ * Returns whether the "Default" group is overridden by a dynamic group
+ * sequence obtained by the validated objects.
+ *
+ * If this method returns true, the class must implement
+ * {@link \Symfony\Component\Validator\GroupSequenceProviderInterface}.
+ * This interface will be used to obtain the group sequence when an object
+ * of this class is validated.
+ *
+ * @return Boolean Returns true if the "Default" group is overridden by
+ * a dynamic group sequence
+ *
+ * @see \Symfony\Component\Validator\GroupSequenceProviderInterface
+ */
+ public function isGroupSequenceProvider();
+}
diff --git a/src/Symfony/Component/Validator/Mapping/ElementMetadata.php b/src/Symfony/Component/Validator/Mapping/ElementMetadata.php
index 9dedb79fd95e4..1b971c91803c1 100644
--- a/src/Symfony/Component/Validator/Mapping/ElementMetadata.php
+++ b/src/Symfony/Component/Validator/Mapping/ElementMetadata.php
@@ -11,97 +11,14 @@
namespace Symfony\Component\Validator\Mapping;
-use Symfony\Component\Validator\Constraint;
-
-abstract class ElementMetadata
+/**
+ * Contains the metadata of a structural element.
+ *
+ * @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Extend {@link GenericMetadata} instead.
+ */
+abstract class ElementMetadata extends GenericMetadata
{
- /**
- * @var Constraint[]
- */
- public $constraints = array();
-
- /**
- * @var array
- */
- public $constraintsByGroup = array();
-
- /**
- * Returns the names of the properties that should be serialized.
- *
- * @return array
- */
- public function __sleep()
- {
- return array(
- 'constraints',
- 'constraintsByGroup',
- );
- }
-
- /**
- * Clones this object.
- */
- public function __clone()
- {
- $constraints = $this->constraints;
-
- $this->constraints = array();
- $this->constraintsByGroup = array();
-
- foreach ($constraints as $constraint) {
- $this->addConstraint(clone $constraint);
- }
- }
-
- /**
- * Adds a constraint to this element.
- *
- * @param Constraint $constraint
- *
- * @return ElementMetadata
- */
- public function addConstraint(Constraint $constraint)
- {
- $this->constraints[] = $constraint;
-
- foreach ($constraint->groups as $group) {
- $this->constraintsByGroup[$group][] = $constraint;
- }
-
- return $this;
- }
-
- /**
- * Returns all constraints of this element.
- *
- * @return Constraint[] An array of Constraint instances
- */
- public function getConstraints()
- {
- return $this->constraints;
- }
-
- /**
- * Returns whether this element has any constraints.
- *
- * @return Boolean
- */
- public function hasConstraints()
- {
- return count($this->constraints) > 0;
- }
-
- /**
- * Returns the constraints of the given group and global ones (* group).
- *
- * @param string $group The group name
- *
- * @return array An array with all Constraint instances belonging to the group
- */
- public function findConstraints($group)
- {
- return isset($this->constraintsByGroup[$group])
- ? $this->constraintsByGroup[$group]
- : array();
- }
}
diff --git a/src/Symfony/Component/Validator/Mapping/Factory/BlackHoleMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/Factory/BlackHoleMetadataFactory.php
new file mode 100644
index 0000000000000..5b38d0c98a775
--- /dev/null
+++ b/src/Symfony/Component/Validator/Mapping/Factory/BlackHoleMetadataFactory.php
@@ -0,0 +1,40 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Mapping\Factory;
+
+/**
+ * Metadata factory that does not store metadata.
+ *
+ * This implementation is useful if you want to validate values against
+ * constraints only and you don't need to add constraints to classes and
+ * properties.
+ *
+ * @author Fabien Potencier
+ */
+class BlackHoleMetadataFactory implements MetadataFactoryInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadataFor($value)
+ {
+ throw new \LogicException('This class does not support metadata.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasMetadataFor($value)
+ {
+ return false;
+ }
+}
diff --git a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php
new file mode 100644
index 0000000000000..240b6be29997f
--- /dev/null
+++ b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php
@@ -0,0 +1,165 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Mapping\Factory;
+
+use Symfony\Component\Validator\Exception\NoSuchMetadataException;
+use Symfony\Component\Validator\Mapping\Cache\CacheInterface;
+use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
+use Symfony\Component\Validator\Mapping\MetadataInterface;
+
+/**
+ * Creates new {@link ClassMetadataInterface} instances.
+ *
+ * Whenever {@link getMetadataFor()} is called for the first time with a given
+ * class name or object of that class, a new metadata instance is created and
+ * returned. On subsequent requests for the same class, the same metadata
+ * instance will be returned.
+ *
+ * You can optionally pass a {@link LoaderInterface} instance to the constructor.
+ * Whenever a new metadata instance is created, it is passed to the loader,
+ * which can configure the metadata based on configuration loaded from the
+ * filesystem or a database. If you want to use multiple loaders, wrap them in a
+ * {@link Loader\LoaderChain}.
+ *
+ * You can also optionally pass a {@link CacheInterface} instance to the
+ * constructor. This cache will be used for persisting the generated metadata
+ * between multiple PHP requests.
+ *
+ * @author Bernhard Schussek
+ */
+class LazyLoadingMetadataFactory implements MetadataFactoryInterface
+{
+ /**
+ * The loader for loading the class metadata
+ *
+ * @var LoaderInterface
+ */
+ protected $loader;
+
+ /**
+ * The cache for caching class metadata
+ *
+ * @var CacheInterface
+ */
+ protected $cache;
+
+ /**
+ * The loaded metadata, indexed by class name
+ *
+ * @var ClassMetadata[]
+ */
+ protected $loadedClasses = array();
+
+ /**
+ * Creates a new metadata factory.
+ *
+ * @param LoaderInterface|null $loader The loader for configuring new metadata
+ * @param CacheInterface|null $cache The cache for persisting metadata
+ * between multiple PHP requests
+ */
+ public function __construct(LoaderInterface $loader = null, CacheInterface $cache = null)
+ {
+ $this->loader = $loader;
+ $this->cache = $cache;
+ }
+
+ /**
+ * Returns the metadata for the given class name or object.
+ *
+ * If the method was called with the same class name (or an object of that
+ * class) before, the same metadata instance is returned.
+ *
+ * If the factory was configured with a cache, this method will first look
+ * for an existing metadata instance in the cache. If an existing instance
+ * is found, it will be returned without further ado.
+ *
+ * Otherwise, a new metadata instance is created. If the factory was
+ * configured with a loader, the metadata is passed to the
+ * {@link LoaderInterface::loadClassMetadata()} method for further
+ * configuration. At last, the new object is returned.
+ *
+ * @param string|object $value A class name or an object
+ *
+ * @return MetadataInterface The metadata for the value
+ *
+ * @throws NoSuchMetadataException If no metadata exists for the given value
+ */
+ public function getMetadataFor($value)
+ {
+ if (!is_object($value) && !is_string($value)) {
+ throw new NoSuchMetadataException(sprintf('Cannot create metadata for non-objects. Got: %s', gettype($value)));
+ }
+
+ $class = ltrim(is_object($value) ? get_class($value) : $value, '\\');
+
+ if (isset($this->loadedClasses[$class])) {
+ return $this->loadedClasses[$class];
+ }
+
+ if (null !== $this->cache && false !== ($this->loadedClasses[$class] = $this->cache->read($class))) {
+ return $this->loadedClasses[$class];
+ }
+
+ if (!class_exists($class) && !interface_exists($class)) {
+ throw new NoSuchMetadataException(sprintf('The class or interface "%s" does not exist.', $class));
+ }
+
+ $metadata = new ClassMetadata($class);
+
+ // Include constraints from the parent class
+ if ($parent = $metadata->getReflectionClass()->getParentClass()) {
+ $metadata->mergeConstraints($this->getMetadataFor($parent->name));
+ }
+
+ // Include constraints from all implemented interfaces
+ foreach ($metadata->getReflectionClass()->getInterfaces() as $interface) {
+ if ('Symfony\Component\Validator\GroupSequenceProviderInterface' === $interface->name) {
+ continue;
+ }
+ $metadata->mergeConstraints($this->getMetadataFor($interface->name));
+ }
+
+ if (null !== $this->loader) {
+ $this->loader->loadClassMetadata($metadata);
+ }
+
+ if (null !== $this->cache) {
+ $this->cache->write($metadata);
+ }
+
+ return $this->loadedClasses[$class] = $metadata;
+ }
+
+ /**
+ * Returns whether the factory is able to return metadata for the given
+ * class name or object.
+ *
+ * @param string|object $value A class name or an object
+ *
+ * @return Boolean Whether metadata can be returned for that class
+ */
+ public function hasMetadataFor($value)
+ {
+ if (!is_object($value) && !is_string($value)) {
+ return false;
+ }
+
+ $class = ltrim(is_object($value) ? get_class($value) : $value, '\\');
+
+ if (class_exists($class) || interface_exists($class)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/Symfony/Component/Validator/Mapping/Factory/MetadataFactoryInterface.php b/src/Symfony/Component/Validator/Mapping/Factory/MetadataFactoryInterface.php
new file mode 100644
index 0000000000000..ef25174d0ecc2
--- /dev/null
+++ b/src/Symfony/Component/Validator/Mapping/Factory/MetadataFactoryInterface.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Mapping\Factory;
+
+use Symfony\Component\Validator\MetadataFactoryInterface as LegacyMetadataFactoryInterface;
+
+/**
+ * Returns {@link MetadataInterface} instances for values.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+interface MetadataFactoryInterface extends LegacyMetadataFactoryInterface
+{
+}
diff --git a/src/Symfony/Component/Validator/Mapping/GenericMetadata.php b/src/Symfony/Component/Validator/Mapping/GenericMetadata.php
new file mode 100644
index 0000000000000..33b40fbd369c6
--- /dev/null
+++ b/src/Symfony/Component/Validator/Mapping/GenericMetadata.php
@@ -0,0 +1,248 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Mapping;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\Traverse;
+use Symfony\Component\Validator\Constraints\Valid;
+use Symfony\Component\Validator\Exception\BadMethodCallException;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\ValidationVisitorInterface;
+
+/**
+ * A generic container of {@link Constraint} objects.
+ *
+ * This class supports serialization and cloning.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+class GenericMetadata implements MetadataInterface
+{
+ /**
+ * @var Constraint[]
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getConstraints()} and {@link findConstraints()} instead.
+ */
+ public $constraints = array();
+
+ /**
+ * @var array
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link findConstraints()} instead.
+ */
+ public $constraintsByGroup = array();
+
+ /**
+ * The strategy for cascading objects.
+ *
+ * By default, objects are not cascaded.
+ *
+ * @var integer
+ *
+ * @see CascadingStrategy
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getCascadingStrategy()} instead.
+ */
+ public $cascadingStrategy = CascadingStrategy::NONE;
+
+ /**
+ * The strategy for traversing traversable objects.
+ *
+ * By default, traversable objects are not traversed.
+ *
+ * @var integer
+ *
+ * @see TraversalStrategy
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getTraversalStrategy()} instead.
+ */
+ public $traversalStrategy = TraversalStrategy::NONE;
+
+ /**
+ * Returns the names of the properties that should be serialized.
+ *
+ * @return string[]
+ */
+ public function __sleep()
+ {
+ return array(
+ 'constraints',
+ 'constraintsByGroup',
+ 'cascadingStrategy',
+ 'traversalStrategy',
+ );
+ }
+
+ /**
+ * Clones this object.
+ */
+ public function __clone()
+ {
+ $constraints = $this->constraints;
+
+ $this->constraints = array();
+ $this->constraintsByGroup = array();
+
+ foreach ($constraints as $constraint) {
+ $this->addConstraint(clone $constraint);
+ }
+ }
+
+ /**
+ * Adds a constraint.
+ *
+ * If the constraint {@link Valid} is added, the cascading strategy will be
+ * changed to {@link CascadingStrategy::CASCADE}. Depending on the
+ * properties $traverse and $deep of that constraint, the traversal strategy
+ * will be set to one of the following:
+ *
+ * - {@link TraversalStrategy::IMPLICIT} if $traverse is enabled and $deep
+ * is enabled
+ * - {@link TraversalStrategy::IMPLICIT} | {@link TraversalStrategy::STOP_RECURSION}
+ * if $traverse is enabled, but $deep is disabled
+ * - {@link TraversalStrategy::NONE} if $traverse is disabled
+ *
+ * @param Constraint $constraint The constraint to add
+ *
+ * @return GenericMetadata This object
+ *
+ * @throws ConstraintDefinitionException When trying to add the
+ * {@link Traverse} constraint
+ */
+ public function addConstraint(Constraint $constraint)
+ {
+ if ($constraint instanceof Traverse) {
+ throw new ConstraintDefinitionException(sprintf(
+ 'The constraint "%s" can only be put on classes. Please use '.
+ '"Symfony\Component\Validator\Constraints\Valid" instead.',
+ get_class($constraint)
+ ));
+ }
+
+ if ($constraint instanceof Valid) {
+ $this->cascadingStrategy = CascadingStrategy::CASCADE;
+
+ if ($constraint->traverse) {
+ // Traverse unless the value is not traversable
+ $this->traversalStrategy = TraversalStrategy::IMPLICIT;
+
+ if (!$constraint->deep) {
+ $this->traversalStrategy |= TraversalStrategy::STOP_RECURSION;
+ }
+ } else {
+ $this->traversalStrategy = TraversalStrategy::NONE;
+ }
+
+ return $this;
+ }
+
+ $this->constraints[] = $constraint;
+
+ foreach ($constraint->groups as $group) {
+ $this->constraintsByGroup[$group][] = $constraint;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Adds an list of constraints.
+ *
+ * @param Constraint[] $constraints The constraints to add
+ *
+ * @return GenericMetadata This object
+ */
+ public function addConstraints(array $constraints)
+ {
+ foreach ($constraints as $constraint) {
+ $this->addConstraint($constraint);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns all constraints of this element.
+ *
+ * @return Constraint[] A list of Constraint instances
+ */
+ public function getConstraints()
+ {
+ return $this->constraints;
+ }
+
+ /**
+ * Returns whether this element has any constraints.
+ *
+ * @return Boolean
+ */
+ public function hasConstraints()
+ {
+ return count($this->constraints) > 0;
+ }
+
+ /**
+ * Returns the constraints of the given group and global ones (* group).
+ *
+ * @param string $group The group name
+ *
+ * @return Constraint[] An list of all the Constraint instances belonging
+ * to the group
+ */
+ public function findConstraints($group)
+ {
+ return isset($this->constraintsByGroup[$group])
+ ? $this->constraintsByGroup[$group]
+ : array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCascadingStrategy()
+ {
+ return $this->cascadingStrategy;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTraversalStrategy()
+ {
+ return $this->traversalStrategy;
+ }
+
+ /**
+ * Exists for compatibility with the deprecated
+ * {@link Symfony\Component\Validator\MetadataInterface}.
+ *
+ * Should not be used.
+ *
+ * @throws BadMethodCallException
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * Will be removed in Symfony 3.0.
+ */
+ public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath)
+ {
+ throw new BadMethodCallException('Not supported.');
+ }
+}
diff --git a/src/Symfony/Component/Validator/Mapping/GetterMetadata.php b/src/Symfony/Component/Validator/Mapping/GetterMetadata.php
index 4bd609d035fae..122e246bdf652 100644
--- a/src/Symfony/Component/Validator/Mapping/GetterMetadata.php
+++ b/src/Symfony/Component/Validator/Mapping/GetterMetadata.php
@@ -13,6 +13,23 @@
use Symfony\Component\Validator\Exception\ValidatorException;
+/**
+ * Stores all metadata needed for validating a class property via its getter
+ * method.
+ *
+ * A property getter is any method that is equal to the property's name,
+ * prefixed with either "get" or "is". That method will be used to access the
+ * property's value.
+ *
+ * The getter will be invoked by reflection, so the access of private and
+ * protected getters is supported.
+ *
+ * This class supports serialization and cloning.
+ *
+ * @author Bernhard Schussek
+ *
+ * @see PropertyMetadataInterface
+ */
class GetterMetadata extends MemberMetadata
{
/**
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php
index 9b5093e1bd928..24591d6be5487 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/AbstractLoader.php
@@ -11,8 +11,8 @@
namespace Symfony\Component\Validator\Mapping\Loader;
-use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\MappingException;
abstract class AbstractLoader implements LoaderInterface
{
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php
index 3a624072e0242..9cd86a1a66002 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/AnnotationLoader.php
@@ -12,12 +12,12 @@
namespace Symfony\Component\Validator\Mapping\Loader;
use Doctrine\Common\Annotations\Reader;
+use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Callback;
-use Symfony\Component\Validator\Exception\MappingException;
-use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\GroupSequence;
use Symfony\Component\Validator\Constraints\GroupSequenceProvider;
-use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Exception\MappingException;
+use Symfony\Component\Validator\Mapping\ClassMetadata;
class AnnotationLoader implements LoaderInterface
{
diff --git a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php
index 3f157c924195d..0cf33e0edff65 100644
--- a/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php
+++ b/src/Symfony/Component/Validator/Mapping/Loader/XmlFileLoader.php
@@ -11,9 +11,9 @@
namespace Symfony\Component\Validator\Mapping\Loader;
+use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Validator\Exception\MappingException;
use Symfony\Component\Validator\Mapping\ClassMetadata;
-use Symfony\Component\Config\Util\XmlUtils;
class XmlFileLoader extends FileLoader
{
diff --git a/src/Symfony/Component/Validator/Mapping/MemberMetadata.php b/src/Symfony/Component/Validator/Mapping/MemberMetadata.php
index c30a87ee06269..9b4860cfaf859 100644
--- a/src/Symfony/Component/Validator/Mapping/MemberMetadata.php
+++ b/src/Symfony/Component/Validator/Mapping/MemberMetadata.php
@@ -11,21 +11,54 @@
namespace Symfony\Component\Validator\Mapping;
-use Symfony\Component\Validator\ValidationVisitorInterface;
-use Symfony\Component\Validator\ClassBasedInterface;
-use Symfony\Component\Validator\PropertyMetadataInterface;
use Symfony\Component\Validator\Constraint;
-use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\ValidationVisitorInterface;
-abstract class MemberMetadata extends ElementMetadata implements PropertyMetadataInterface, ClassBasedInterface
+/**
+ * Stores all metadata needed for validating a class property.
+ *
+ * The method of accessing the property's value must be specified by subclasses
+ * by implementing the {@link newReflectionMember()} method.
+ *
+ * This class supports serialization and cloning.
+ *
+ * @author Bernhard Schussek
+ *
+ * @see PropertyMetadataInterface
+ */
+abstract class MemberMetadata extends ElementMetadata implements PropertyMetadataInterface
{
+ /**
+ * @var string
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getClassName()} instead.
+ */
public $class;
+
+ /**
+ * @var string
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getName()} instead.
+ */
public $name;
+
+ /**
+ * @var string
+ *
+ * @internal This property is public in order to reduce the size of the
+ * class' serialized representation. Do not access it. Use
+ * {@link getPropertyName()} instead.
+ */
public $property;
- public $cascaded = false;
- public $collectionCascaded = false;
- public $collectionCascadedDeeply = false;
+
+ /**
+ * @var \ReflectionMethod[]|\ReflectionProperty[]
+ */
private $reflMember = array();
/**
@@ -42,6 +75,11 @@ public function __construct($class, $name, $property)
$this->property = $property;
}
+ /**
+ * {@inheritdoc}
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ */
public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath, $propagatedGroup = null)
{
$visitor->visit($this, $value, $group, $propertyPath);
@@ -63,22 +101,13 @@ public function addConstraint(Constraint $constraint)
));
}
- if ($constraint instanceof Valid) {
- $this->cascaded = true;
- /* @var Valid $constraint */
- $this->collectionCascaded = $constraint->traverse;
- $this->collectionCascadedDeeply = $constraint->deep;
- } else {
- parent::addConstraint($constraint);
- }
+ parent::addConstraint($constraint);
return $this;
}
/**
- * Returns the names of the properties that should be serialized
- *
- * @return array
+ * {@inheritdoc}
*/
public function __sleep()
{
@@ -86,14 +115,11 @@ public function __sleep()
'class',
'name',
'property',
- 'cascaded',
- 'collectionCascaded',
- 'collectionCascadedDeeply',
));
}
/**
- * Returns the name of the member
+ * Returns the name of the member.
*
* @return string
*/
@@ -103,9 +129,7 @@ public function getName()
}
/**
- * Returns the class this member is defined on
- *
- * @return string
+ * {@inheritdoc}
*/
public function getClassName()
{
@@ -113,9 +137,7 @@ public function getClassName()
}
/**
- * Returns the name of the property this member belongs to
- *
- * @return string The property name
+ * {@inheritdoc}
*/
public function getPropertyName()
{
@@ -123,7 +145,7 @@ public function getPropertyName()
}
/**
- * Returns whether this member is public
+ * Returns whether this member is public.
*
* @param object|string $objectOrClassName The object or the class name
*
@@ -147,7 +169,7 @@ public function isProtected($objectOrClassName)
}
/**
- * Returns whether this member is private
+ * Returns whether this member is private.
*
* @param object|string $objectOrClassName The object or the class name
*
@@ -159,43 +181,52 @@ public function isPrivate($objectOrClassName)
}
/**
- * Returns whether objects stored in this member should be validated
+ * Returns whether objects stored in this member should be validated.
*
* @return Boolean
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link getCascadingStrategy()} instead.
*/
public function isCascaded()
{
- return $this->cascaded;
+ return (boolean) ($this->cascadingStrategy & CascadingStrategy::CASCADE);
}
/**
* Returns whether arrays or traversable objects stored in this member
- * should be traversed and validated in each entry
+ * should be traversed and validated in each entry.
*
* @return Boolean
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link getTraversalStrategy()} instead.
*/
public function isCollectionCascaded()
{
- return $this->collectionCascaded;
+ return (boolean) ($this->traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE));
}
/**
* Returns whether arrays or traversable objects stored in this member
- * should be traversed recursively for inner arrays/traversable objects
+ * should be traversed recursively for inner arrays/traversable objects.
*
* @return Boolean
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link getTraversalStrategy()} instead.
*/
public function isCollectionCascadedDeeply()
{
- return $this->collectionCascadedDeeply;
+ return !($this->traversalStrategy & TraversalStrategy::STOP_RECURSION);
}
/**
- * Returns the Reflection instance of the member
+ * Returns the reflection instance for accessing the member's value.
*
* @param object|string $objectOrClassName The object or the class name
*
- * @return object
+ * @return \ReflectionMethod|\ReflectionProperty The reflection instance
*/
public function getReflectionMember($objectOrClassName)
{
@@ -208,11 +239,13 @@ public function getReflectionMember($objectOrClassName)
}
/**
- * Creates a new Reflection instance for the member
+ * Creates a new reflection instance for accessing the member's value.
+ *
+ * Must be implemented by subclasses.
*
* @param object|string $objectOrClassName The object or the class name
*
- * @return mixed Reflection class
+ * @return \ReflectionMethod|\ReflectionProperty The reflection instance
*/
abstract protected function newReflectionMember($objectOrClassName);
}
diff --git a/src/Symfony/Component/Validator/Mapping/MetadataInterface.php b/src/Symfony/Component/Validator/Mapping/MetadataInterface.php
new file mode 100644
index 0000000000000..e947c8dfe35a5
--- /dev/null
+++ b/src/Symfony/Component/Validator/Mapping/MetadataInterface.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Mapping;
+
+use Symfony\Component\Validator\MetadataInterface as LegacyMetadataInterface;
+
+/**
+ * A container for validation metadata.
+ *
+ * Most importantly, the metadata stores the constraints against which an object
+ * and its properties should be validated.
+ *
+ * Additionally, the metadata stores whether objects should be validated
+ * against their class' metadata and whether traversable objects should be
+ * traversed or not.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @see CascadingStrategy
+ * @see TraversalStrategy
+ */
+interface MetadataInterface extends LegacyMetadataInterface
+{
+ /**
+ * Returns the strategy for cascading objects.
+ *
+ * @return integer The cascading strategy
+ *
+ * @see CascadingStrategy
+ */
+ public function getCascadingStrategy();
+
+ /**
+ * Returns the strategy for traversing traversable objects.
+ *
+ * @return integer The traversal strategy
+ *
+ * @see TraversalStrategy
+ */
+ public function getTraversalStrategy();
+}
diff --git a/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php b/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php
index 468f196f04a8e..f875cd68cea35 100644
--- a/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php
+++ b/src/Symfony/Component/Validator/Mapping/PropertyMetadata.php
@@ -13,6 +13,19 @@
use Symfony\Component\Validator\Exception\ValidatorException;
+/**
+ * Stores all metadata needed for validating a class property.
+ *
+ * The value of the property is obtained by directly accessing the property.
+ * The property will be accessed by reflection, so the access of private and
+ * protected properties is supported.
+ *
+ * This class supports serialization and cloning.
+ *
+ * @author Bernhard Schussek
+ *
+ * @see PropertyMetadataInterface
+ */
class PropertyMetadata extends MemberMetadata
{
/**
diff --git a/src/Symfony/Component/Validator/Mapping/PropertyMetadataInterface.php b/src/Symfony/Component/Validator/Mapping/PropertyMetadataInterface.php
new file mode 100644
index 0000000000000..79e2c799de602
--- /dev/null
+++ b/src/Symfony/Component/Validator/Mapping/PropertyMetadataInterface.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Mapping;
+
+use Symfony\Component\Validator\ClassBasedInterface;
+use Symfony\Component\Validator\PropertyMetadataInterface as LegacyPropertyMetadataInterface;
+
+/**
+ * Stores all metadata needed for validating the value of a class property.
+ *
+ * Most importantly, the metadata stores the constraints against which the
+ * property's value should be validated.
+ *
+ * Additionally, the metadata stores whether objects stored in the property
+ * should be validated against their class' metadata and whether traversable
+ * objects should be traversed or not.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @see MetadataInterface
+ * @see CascadingStrategy
+ * @see TraversalStrategy
+ */
+interface PropertyMetadataInterface extends MetadataInterface, LegacyPropertyMetadataInterface, ClassBasedInterface
+{
+}
diff --git a/src/Symfony/Component/Validator/Mapping/TraversalStrategy.php b/src/Symfony/Component/Validator/Mapping/TraversalStrategy.php
new file mode 100644
index 0000000000000..d44733b61c5e7
--- /dev/null
+++ b/src/Symfony/Component/Validator/Mapping/TraversalStrategy.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Mapping;
+
+/**
+ * Specifies whether and how a traversable object should be traversed.
+ *
+ * If the node traverser traverses a node whose value is an instance of
+ * {@link \Traversable}, and if that node is either a class node or if
+ * cascading is enabled, then the node's traversal strategy will be checked.
+ * Depending on the requested traversal strategy, the node traverser will
+ * iterate over the object and cascade each object or collection returned by
+ * the iterator.
+ *
+ * The traversal strategy is ignored for arrays. Arrays are always iterated.
+ *
+ * @since 2.1
+ * @author Bernhard Schussek
+ *
+ * @see CascadingStrategy
+ */
+class TraversalStrategy
+{
+ /**
+ * Specifies that a node's value should be iterated only if it is an
+ * instance of {@link \Traversable}.
+ */
+ const IMPLICIT = 1;
+
+ /**
+ * Specifies that a node's value should never be iterated.
+ */
+ const NONE = 2;
+
+ /**
+ * Specifies that a node's value should always be iterated. If the value is
+ * not an instance of {@link \Traversable}, an exception should be thrown.
+ */
+ const TRAVERSE = 4;
+
+ /**
+ * Specifies that nested instances of {@link \Traversable} should never be
+ * iterated. Can be combined with {@link IMPLICIT} or {@link TRAVERSE}.
+ *
+ * @deprecated This constant was added for backwards compatibility only.
+ * It will be removed in Symfony 3.0.
+ */
+ const STOP_RECURSION = 8;
+
+ /**
+ * Not instantiable.
+ */
+ private function __construct()
+ {
+ }
+}
diff --git a/src/Symfony/Component/Validator/MetadataFactoryInterface.php b/src/Symfony/Component/Validator/MetadataFactoryInterface.php
index 6dbab06ab74e2..b025f19ddddd4 100644
--- a/src/Symfony/Component/Validator/MetadataFactoryInterface.php
+++ b/src/Symfony/Component/Validator/MetadataFactoryInterface.php
@@ -15,26 +15,29 @@
* Returns {@link MetadataInterface} instances for values.
*
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Mapping\Factory\MetadataFactoryInterface} instead.
*/
interface MetadataFactoryInterface
{
/**
* Returns the metadata for the given value.
*
- * @param mixed $value Some value.
+ * @param mixed $value Some value
*
- * @return MetadataInterface The metadata for the value.
+ * @return MetadataInterface The metadata for the value
*
- * @throws Exception\NoSuchMetadataException If no metadata exists for the value.
+ * @throws Exception\NoSuchMetadataException If no metadata exists for the given value
*/
public function getMetadataFor($value);
/**
- * Returns whether metadata exists for the given value.
+ * Returns whether the class is able to return metadata for the given value.
*
- * @param mixed $value Some value.
+ * @param mixed $value Some value
*
- * @return Boolean Whether metadata exists for the value.
+ * @return Boolean Whether metadata can be returned for that value
*/
public function hasMetadataFor($value);
}
diff --git a/src/Symfony/Component/Validator/MetadataInterface.php b/src/Symfony/Component/Validator/MetadataInterface.php
index a5d65048b7816..60abfeb42a9f0 100644
--- a/src/Symfony/Component/Validator/MetadataInterface.php
+++ b/src/Symfony/Component/Validator/MetadataInterface.php
@@ -41,6 +41,9 @@
* again.
*
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Mapping\MetadataInterface} instead.
*/
interface MetadataInterface
{
@@ -50,19 +53,21 @@ interface MetadataInterface
* Calls {@link ValidationVisitorInterface::visit} and then forwards the
* accept()-call to all property metadata instances.
*
- * @param ValidationVisitorInterface $visitor The visitor implementing the validation logic.
- * @param mixed $value The value to validate.
- * @param string|string[] $group The validation group to validate in.
- * @param string $propertyPath The current property path in the validation graph.
+ * @param ValidationVisitorInterface $visitor The visitor implementing the validation logic
+ * @param mixed $value The value to validate
+ * @param string|string[] $group The validation group to validate in
+ * @param string $propertyPath The current property path in the validation graph
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
*/
public function accept(ValidationVisitorInterface $visitor, $value, $group, $propertyPath);
/**
* Returns all constraints for a given validation group.
*
- * @param string $group The validation group.
+ * @param string $group The validation group
*
- * @return Constraint[] A list of constraint instances.
+ * @return Constraint[] A list of constraint instances
*/
public function findConstraints($group);
}
diff --git a/src/Symfony/Component/Validator/PropertyMetadataContainerInterface.php b/src/Symfony/Component/Validator/PropertyMetadataContainerInterface.php
index 20bafb2950fd9..2bb00f2a9ab96 100644
--- a/src/Symfony/Component/Validator/PropertyMetadataContainerInterface.php
+++ b/src/Symfony/Component/Validator/PropertyMetadataContainerInterface.php
@@ -15,6 +15,9 @@
* A container for {@link PropertyMetadataInterface} instances.
*
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Mapping\ClassMetadataInterface} instead.
*/
interface PropertyMetadataContainerInterface
{
diff --git a/src/Symfony/Component/Validator/PropertyMetadataInterface.php b/src/Symfony/Component/Validator/PropertyMetadataInterface.php
index eaac1a7121103..c18ae83a05e81 100644
--- a/src/Symfony/Component/Validator/PropertyMetadataInterface.php
+++ b/src/Symfony/Component/Validator/PropertyMetadataInterface.php
@@ -23,6 +23,9 @@
* @author Bernhard Schussek
*
* @see MetadataInterface
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Mapping\PropertyMetadataInterface} instead.
*/
interface PropertyMetadataInterface extends MetadataInterface
{
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/AllValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/AllValidatorTest.php
index eaa9044e9ef52..3a654a3b457eb 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/AllValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/AllValidatorTest.php
@@ -11,11 +11,10 @@
namespace Symfony\Component\Validator\Tests\Constraints;
-use Symfony\Component\Validator\ExecutionContext;
-use Symfony\Component\Validator\Constraints\Range;
-use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\All;
use Symfony\Component\Validator\Constraints\AllValidator;
+use Symfony\Component\Validator\Constraints\NotNull;
+use Symfony\Component\Validator\Constraints\Range;
class AllValidatorTest extends \PHPUnit_Framework_TestCase
{
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CallbackValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CallbackValidatorTest.php
index cdcd49bb58ed8..06883e3525b15 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/CallbackValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/CallbackValidatorTest.php
@@ -11,9 +11,10 @@
namespace Symfony\Component\Validator\Tests\Constraints;
-use Symfony\Component\Validator\ExecutionContext;
+use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\CallbackValidator;
+use Symfony\Component\Validator\ExecutionContext;
class CallbackValidatorTest_Class
{
@@ -128,6 +129,23 @@ public function testClosure()
$this->validator->validate($object, $constraint);
}
+ public function testClosureNullObject()
+ {
+ $constraint = new Callback(function ($object, ExecutionContext $context) {
+ $context->addViolation('My message', array('{{ value }}' => 'foobar'), 'invalidValue');
+
+ return false;
+ });
+
+ $this->context->expects($this->once())
+ ->method('addViolation')
+ ->with('My message', array(
+ '{{ value }}' => 'foobar',
+ ));
+
+ $this->validator->validate(null, $constraint);
+ }
+
public function testClosureExplicitName()
{
$object = new CallbackValidatorTest_Object();
@@ -162,6 +180,19 @@ public function testArrayCallable()
$this->validator->validate($object, $constraint);
}
+ public function testArrayCallableNullObject()
+ {
+ $constraint = new Callback(array(__CLASS__.'_Class', 'validateCallback'));
+
+ $this->context->expects($this->once())
+ ->method('addViolation')
+ ->with('Callback message', array(
+ '{{ value }}' => 'foobar',
+ ));
+
+ $this->validator->validate(null, $constraint);
+ }
+
public function testArrayCallableExplicitName()
{
$object = new CallbackValidatorTest_Object();
@@ -320,8 +351,9 @@ public function testExpectEitherCallbackOrMethods()
public function testConstraintGetTargets()
{
$constraint = new Callback(array('foo'));
+ $targets = array(Constraint::CLASS_CONSTRAINT, Constraint::PROPERTY_CONSTRAINT);
- $this->assertEquals('class', $constraint->getTargets());
+ $this->assertEquals($targets, $constraint->getTargets());
}
// Should succeed. Needed when defining constraints as annotations.
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CollectionTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CollectionTest.php
index da868d3cfdd1f..4b485a9b108b7 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/CollectionTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/CollectionTest.php
@@ -12,9 +12,9 @@
namespace Symfony\Component\Validator\Tests\Constraints;
use Symfony\Component\Validator\Constraints\Collection;
-use Symfony\Component\Validator\Constraints\Required;
-use Symfony\Component\Validator\Constraints\Optional;
use Symfony\Component\Validator\Constraints\Email;
+use Symfony\Component\Validator\Constraints\Optional;
+use Symfony\Component\Validator\Constraints\Required;
use Symfony\Component\Validator\Constraints\Valid;
/**
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php
index 4a13234b69550..4dc5f7e8bc7a5 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/CollectionValidatorTest.php
@@ -11,12 +11,12 @@
namespace Symfony\Component\Validator\Tests\Constraints;
-use Symfony\Component\Validator\Constraints\Range;
-use Symfony\Component\Validator\Constraints\NotNull;
-use Symfony\Component\Validator\Constraints\Required;
-use Symfony\Component\Validator\Constraints\Optional;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\CollectionValidator;
+use Symfony\Component\Validator\Constraints\NotNull;
+use Symfony\Component\Validator\Constraints\Optional;
+use Symfony\Component\Validator\Constraints\Range;
+use Symfony\Component\Validator\Constraints\Required;
abstract class CollectionValidatorTest extends \PHPUnit_Framework_TestCase
{
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CountryValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CountryValidatorTest.php
index 95851e8097ad7..151fca79f4b2d 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/CountryValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/CountryValidatorTest.php
@@ -22,7 +22,7 @@ class CountryValidatorTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
- IntlTestHelper::requireIntl($this);
+ IntlTestHelper::requireFullIntl($this);
$this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
$this->validator = new CountryValidator();
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/CurrencyValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/CurrencyValidatorTest.php
index ea6c2eb43ac02..00b200e1e9463 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/CurrencyValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/CurrencyValidatorTest.php
@@ -22,7 +22,7 @@ class CurrencyValidatorTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
- IntlTestHelper::requireIntl($this);
+ IntlTestHelper::requireFullIntl($this);
$this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
$this->validator = new CurrencyValidator();
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php
index 0927aedacdd5c..f5178cc029af7 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/FileValidatorTest.php
@@ -11,9 +11,9 @@
namespace Symfony\Component\Validator\Tests\Constraints;
+use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Constraints\FileValidator;
-use Symfony\Component\HttpFoundation\File\UploadedFile;
abstract class FileValidatorTest extends \PHPUnit_Framework_TestCase
{
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/GroupSequenceTest.php b/src/Symfony/Component/Validator/Tests/Constraints/GroupSequenceTest.php
new file mode 100644
index 0000000000000..85b60b5eee3d5
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Constraints/GroupSequenceTest.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Constraints;
+
+use Symfony\Component\Validator\Constraints\GroupSequence;
+
+/**
+ * @author Bernhard Schussek
+ */
+class GroupSequenceTest extends \PHPUnit_Framework_TestCase
+{
+ public function testCreate()
+ {
+ $sequence = new GroupSequence(array('Group 1', 'Group 2'));
+
+ $this->assertSame(array('Group 1', 'Group 2'), $sequence->groups);
+ }
+
+ public function testCreateDoctrineStyle()
+ {
+ $sequence = new GroupSequence(array('value' => array('Group 1', 'Group 2')));
+
+ $this->assertSame(array('Group 1', 'Group 2'), $sequence->groups);
+ }
+
+ public function testIterate()
+ {
+ $sequence = new GroupSequence(array('Group 1', 'Group 2'));
+
+ $this->assertSame(array('Group 1', 'Group 2'), iterator_to_array($sequence));
+ }
+
+ public function testCount()
+ {
+ $sequence = new GroupSequence(array('Group 1', 'Group 2'));
+
+ $this->assertCount(2, $sequence);
+ }
+
+ public function testArrayAccess()
+ {
+ $sequence = new GroupSequence(array('Group 1', 'Group 2'));
+
+ $this->assertSame('Group 1', $sequence[0]);
+ $this->assertSame('Group 2', $sequence[1]);
+ $this->assertTrue(isset($sequence[0]));
+ $this->assertFalse(isset($sequence[2]));
+ unset($sequence[0]);
+ $this->assertFalse(isset($sequence[0]));
+ $sequence[] = 'Group 3';
+ $this->assertTrue(isset($sequence[2]));
+ $this->assertSame('Group 3', $sequence[2]);
+ $sequence[0] = 'Group 1';
+ $this->assertTrue(isset($sequence[0]));
+ $this->assertSame('Group 1', $sequence[0]);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\OutOfBoundsException
+ */
+ public function testGetExpectsExistingKey()
+ {
+ $sequence = new GroupSequence(array('Group 1', 'Group 2'));
+
+ $sequence[2];
+ }
+
+ public function testUnsetIgnoresNonExistingKeys()
+ {
+ $sequence = new GroupSequence(array('Group 1', 'Group 2'));
+
+ // should not fail
+ unset($sequence[2]);
+ }
+}
diff --git a/src/Symfony/Component/Validator/Tests/Constraints/LanguageValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/LanguageValidatorTest.php
index 3588887d74998..af5a05fbcceeb 100644
--- a/src/Symfony/Component/Validator/Tests/Constraints/LanguageValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/Constraints/LanguageValidatorTest.php
@@ -22,7 +22,7 @@ class LanguageValidatorTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
- IntlTestHelper::requireIntl($this);
+ IntlTestHelper::requireFullIntl($this);
$this->context = $this->getMock('Symfony\Component\Validator\ExecutionContext', array(), array(), '', false);
$this->validator = new LanguageValidator();
diff --git a/src/Symfony/Component/Validator/Tests/ExecutionContextTest.php b/src/Symfony/Component/Validator/Tests/ExecutionContextTest.php
index dcc9c027bf75b..d58bf25ba6b2f 100644
--- a/src/Symfony/Component/Validator/Tests/ExecutionContextTest.php
+++ b/src/Symfony/Component/Validator/Tests/ExecutionContextTest.php
@@ -11,13 +11,13 @@
namespace Symfony\Component\Validator\Tests;
+use Symfony\Component\Validator\Constraints\Collection;
+use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ExecutionContext;
-use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\ValidationVisitor;
-use Symfony\Component\Validator\ConstraintValidatorFactory;
class ExecutionContextTest extends \PHPUnit_Framework_TestCase
{
diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php
index fbd879a94eaf1..d841f5dc9d78e 100644
--- a/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/Entity.php
@@ -32,9 +32,10 @@ class Entity extends EntityParent implements EntityInterface
* })
* @Assert\Choice(choices={"A", "B"}, message="Must be one of %choices%")
*/
- protected $firstName;
+ public $firstName;
protected $lastName;
public $reference;
+ public $reference2;
private $internal;
public $data = 'Overridden data';
@@ -48,6 +49,11 @@ public function getInternal()
return $this->internal.' from getter';
}
+ public function setLastName($lastName)
+ {
+ $this->lastName = $lastName;
+ }
+
/**
* @Assert\NotNull
*/
diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/FakeClassMetadata.php b/src/Symfony/Component/Validator/Tests/Fixtures/FakeClassMetadata.php
new file mode 100644
index 0000000000000..5ae0e68a777e8
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/FakeClassMetadata.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Fixtures;
+
+use Symfony\Component\Validator\Mapping\ClassMetadata;
+
+class FakeClassMetadata extends ClassMetadata
+{
+ public function addPropertyMetadata($propertyName, $metadata)
+ {
+ if (!isset($this->members[$propertyName])) {
+ $this->members[$propertyName] = array();
+ }
+
+ $this->members[$propertyName][] = $metadata;
+ }
+}
diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php b/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php
index ba39823be6e16..e3f0d9a007800 100644
--- a/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/FakeMetadataFactory.php
@@ -11,9 +11,9 @@
namespace Symfony\Component\Validator\Tests\Fixtures;
-use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
-use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\MetadataInterface;
class FakeMetadataFactory implements MetadataFactoryInterface
{
@@ -21,7 +21,10 @@ class FakeMetadataFactory implements MetadataFactoryInterface
public function getMetadataFor($class)
{
+ $hash = null;
+
if (is_object($class)) {
+ $hash = spl_object_hash($class);
$class = get_class($class);
}
@@ -30,6 +33,10 @@ public function getMetadataFor($class)
}
if (!isset($this->metadatas[$class])) {
+ if (isset($this->metadatas[$hash])) {
+ return $this->metadatas[$hash];
+ }
+
throw new NoSuchMetadataException(sprintf('No metadata for "%s"', $class));
}
@@ -38,7 +45,10 @@ public function getMetadataFor($class)
public function hasMetadataFor($class)
{
+ $hash = null;
+
if (is_object($class)) {
+ $hash = spl_object_hash($class);
$class = get_class($class);
}
@@ -46,11 +56,17 @@ public function hasMetadataFor($class)
return false;
}
- return isset($this->metadatas[$class]);
+ return isset($this->metadatas[$class]) || isset($this->metadatas[$hash]);
}
- public function addMetadata(ClassMetadata $metadata)
+ public function addMetadata($metadata)
{
$this->metadatas[$metadata->getClassName()] = $metadata;
}
+
+ public function addMetadataForValue($value, MetadataInterface $metadata)
+ {
+ $key = is_object($value) ? spl_object_hash($value) : $value;
+ $this->metadatas[$key] = $metadata;
+ }
}
diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderEntity.php b/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderEntity.php
index ef3711104ad43..2b0beaf9adf98 100644
--- a/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderEntity.php
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/GroupSequenceProviderEntity.php
@@ -22,15 +22,15 @@ class GroupSequenceProviderEntity implements GroupSequenceProviderInterface
public $firstName;
public $lastName;
- protected $groups = array();
+ protected $sequence = array();
- public function setGroups($groups)
+ public function __construct($sequence)
{
- $this->groups = $groups;
+ $this->sequence = $sequence;
}
public function getGroupSequence()
{
- return $this->groups;
+ return $this->sequence;
}
}
diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/LegacyClassMetadata.php b/src/Symfony/Component/Validator/Tests/Fixtures/LegacyClassMetadata.php
new file mode 100644
index 0000000000000..6a832a109f99e
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/LegacyClassMetadata.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Fixtures;
+
+use Symfony\Component\Validator\ClassBasedInterface;
+use Symfony\Component\Validator\MetadataInterface;
+use Symfony\Component\Validator\PropertyMetadataContainerInterface;
+
+interface LegacyClassMetadata extends MetadataInterface, PropertyMetadataContainerInterface, ClassBasedInterface
+{
+}
diff --git a/src/Symfony/Component/Validator/Tests/Fixtures/Reference.php b/src/Symfony/Component/Validator/Tests/Fixtures/Reference.php
index f8ea173e019aa..af29735924379 100644
--- a/src/Symfony/Component/Validator/Tests/Fixtures/Reference.php
+++ b/src/Symfony/Component/Validator/Tests/Fixtures/Reference.php
@@ -13,4 +13,17 @@
class Reference
{
+ public $value;
+
+ private $privateValue;
+
+ public function setPrivateValue($privateValue)
+ {
+ $this->privateValue = $privateValue;
+ }
+
+ public function getPrivateValue()
+ {
+ return $this->privateValue;
+ }
}
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Cache/DoctrineCacheTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Cache/DoctrineCacheTest.php
index df2d9f4104ce8..f238a899d6176 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Cache/DoctrineCacheTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Cache/DoctrineCacheTest.php
@@ -11,8 +11,8 @@
namespace Symfony\Component\Validator\Tests\Mapping\Cache;
-use Symfony\Component\Validator\Mapping\Cache\DoctrineCache;
use Doctrine\Common\Cache\ArrayCache;
+use Symfony\Component\Validator\Mapping\Cache\DoctrineCache;
class DoctrineCacheTest extends \PHPUnit_Framework_TestCase
{
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataFactoryTest.php b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataFactoryTest.php
index bee4025d0d279..aee137a1f8c02 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataFactoryTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataFactoryTest.php
@@ -11,11 +11,10 @@
namespace Symfony\Component\Validator\Tests\Mapping;
-use Symfony\Component\Validator\Tests\Fixtures\Entity;
-use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
-use Symfony\Component\Validator\Mapping\ClassMetadataFactory;
use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\Mapping\ClassMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
+use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
class ClassMetadataFactoryTest extends \PHPUnit_Framework_TestCase
{
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php
index 9ead7d134eb60..9579b36b5b560 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/ClassMetadataTest.php
@@ -14,8 +14,6 @@
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\ClassMetadata;
-use Symfony\Component\Validator\Exception\GroupDefinitionException;
-use Symfony\Component\Validator\Tests\Fixtures\Entity;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
use Symfony\Component\Validator\Tests\Fixtures\PropertyConstraint;
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/ElementMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/ElementMetadataTest.php
index 8cf3e6dec4683..9539b0f22cfed 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/ElementMetadataTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/ElementMetadataTest.php
@@ -11,9 +11,9 @@
namespace Symfony\Component\Validator\Tests\Mapping;
+use Symfony\Component\Validator\Mapping\ElementMetadata;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
-use Symfony\Component\Validator\Mapping\ElementMetadata;
class ElementMetadataTest extends \PHPUnit_Framework_TestCase
{
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php
index e4ea6cfc6a498..8da207ff9de57 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/AnnotationLoaderTest.php
@@ -14,10 +14,10 @@
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Validator\Constraints\All;
use Symfony\Component\Validator\Constraints\Callback;
+use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Range;
-use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\True;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php
index 7723349e94d8e..09e6e449e0309 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/FilesLoaderTest.php
@@ -11,8 +11,8 @@
namespace Symfony\Component\Validator\Tests\Mapping\Loader;
-use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
class FilesLoaderTest extends \PHPUnit_Framework_TestCase
{
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php
index e7243edc2c31f..e2b27f0bb6107 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/XmlFileLoaderTest.php
@@ -13,10 +13,10 @@
use Symfony\Component\Validator\Constraints\All;
use Symfony\Component\Validator\Constraints\Callback;
+use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Range;
-use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Regex;
use Symfony\Component\Validator\Constraints\True;
use Symfony\Component\Validator\Mapping\ClassMetadata;
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php
index 1de902a551a40..aeccf0c2836ab 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/Loader/YamlFileLoaderTest.php
@@ -13,10 +13,10 @@
use Symfony\Component\Validator\Constraints\All;
use Symfony\Component\Validator\Constraints\Callback;
+use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\Collection;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Range;
-use Symfony\Component\Validator\Constraints\Choice;
use Symfony\Component\Validator\Constraints\True;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;
diff --git a/src/Symfony/Component/Validator/Tests/Mapping/MemberMetadataTest.php b/src/Symfony/Component/Validator/Tests/Mapping/MemberMetadataTest.php
index bfb402cdee2be..f91088de0b016 100644
--- a/src/Symfony/Component/Validator/Tests/Mapping/MemberMetadataTest.php
+++ b/src/Symfony/Component/Validator/Tests/Mapping/MemberMetadataTest.php
@@ -11,11 +11,11 @@
namespace Symfony\Component\Validator\Tests\Mapping;
-use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
-use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
-use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Mapping\MemberMetadata;
+use Symfony\Component\Validator\Tests\Fixtures\ClassConstraint;
+use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
+use Symfony\Component\Validator\Tests\Fixtures\ConstraintB;
class MemberMetadataTest extends \PHPUnit_Framework_TestCase
{
diff --git a/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php b/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php
deleted file mode 100644
index 2868f57a82cd6..0000000000000
--- a/src/Symfony/Component/Validator/Tests/ValidationVisitorTest.php
+++ /dev/null
@@ -1,564 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Validator\Tests;
-
-use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
-use Symfony\Component\Validator\Constraints\Valid;
-use Symfony\Component\Validator\Tests\Fixtures\Reference;
-use Symfony\Component\Validator\DefaultTranslator;
-use Symfony\Component\Validator\ConstraintViolation;
-use Symfony\Component\Validator\ConstraintViolationList;
-use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint;
-use Symfony\Component\Validator\Tests\Fixtures\ConstraintAValidator;
-use Symfony\Component\Validator\Tests\Fixtures\Entity;
-use Symfony\Component\Validator\Tests\Fixtures\ConstraintA;
-use Symfony\Component\Validator\Mapping\ClassMetadata;
-use Symfony\Component\Validator\ConstraintValidatorFactory;
-use Symfony\Component\Validator\ValidationVisitor;
-
-/**
- * @author Bernhard Schussek
- */
-class ValidationVisitorTest extends \PHPUnit_Framework_TestCase
-{
- const CLASS_NAME = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
-
- /**
- * @var ValidationVisitor
- */
- private $visitor;
-
- /**
- * @var FakeMetadataFactory
- */
- private $metadataFactory;
-
- /**
- * @var ClassMetadata
- */
- private $metadata;
-
- protected function setUp()
- {
- $this->metadataFactory = new FakeMetadataFactory();
- $this->visitor = new ValidationVisitor('Root', $this->metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator());
- $this->metadata = new ClassMetadata(self::CLASS_NAME);
- $this->metadataFactory->addMetadata($this->metadata);
- }
-
- protected function tearDown()
- {
- $this->metadataFactory = null;
- $this->visitor = null;
- $this->metadata = null;
- }
-
- public function testValidatePassesCorrectClassAndProperty()
- {
- $this->metadata->addConstraint(new ConstraintA());
-
- $entity = new Entity();
- $this->visitor->validate($entity, 'Default', '');
-
- $context = ConstraintAValidator::$passedContext;
-
- $this->assertEquals('Symfony\Component\Validator\Tests\Fixtures\Entity', $context->getClassName());
- $this->assertNull($context->getPropertyName());
- }
-
- public function testValidateConstraints()
- {
- $this->metadata->addConstraint(new ConstraintA());
-
- $this->visitor->validate(new Entity(), 'Default', '');
-
- $this->assertCount(1, $this->visitor->getViolations());
- }
-
- public function testValidateTwiceValidatesConstraintsOnce()
- {
- $this->metadata->addConstraint(new ConstraintA());
-
- $entity = new Entity();
-
- $this->visitor->validate($entity, 'Default', '');
- $this->visitor->validate($entity, 'Default', '');
-
- $this->assertCount(1, $this->visitor->getViolations());
- }
-
- public function testValidateDifferentObjectsValidatesTwice()
- {
- $this->metadata->addConstraint(new ConstraintA());
-
- $this->visitor->validate(new Entity(), 'Default', '');
- $this->visitor->validate(new Entity(), 'Default', '');
-
- $this->assertCount(2, $this->visitor->getViolations());
- }
-
- public function testValidateTwiceInDifferentGroupsValidatesTwice()
- {
- $this->metadata->addConstraint(new ConstraintA());
- $this->metadata->addConstraint(new ConstraintA(array('groups' => 'Custom')));
-
- $entity = new Entity();
-
- $this->visitor->validate($entity, 'Default', '');
- $this->visitor->validate($entity, 'Custom', '');
-
- $this->assertCount(2, $this->visitor->getViolations());
- }
-
- public function testValidatePropertyConstraints()
- {
- $this->metadata->addPropertyConstraint('firstName', new ConstraintA());
-
- $this->visitor->validate(new Entity(), 'Default', '');
-
- $this->assertCount(1, $this->visitor->getViolations());
- }
-
- public function testValidateGetterConstraints()
- {
- $this->metadata->addGetterConstraint('lastName', new ConstraintA());
-
- $this->visitor->validate(new Entity(), 'Default', '');
-
- $this->assertCount(1, $this->visitor->getViolations());
- }
-
- public function testValidateInDefaultGroupTraversesGroupSequence()
- {
- $entity = new Entity();
-
- $this->metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
- 'groups' => 'First',
- )));
- $this->metadata->addGetterConstraint('lastName', new FailingConstraint(array(
- 'groups' => 'Default',
- )));
- $this->metadata->setGroupSequence(array('First', $this->metadata->getDefaultGroup()));
-
- $this->visitor->validate($entity, 'Default', '');
-
- // After validation of group "First" failed, no more group was
- // validated
- $violations = new ConstraintViolationList(array(
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- 'firstName',
- ''
- ),
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- public function testValidateInGroupSequencePropagatesDefaultGroup()
- {
- $entity = new Entity();
- $entity->reference = new Reference();
-
- $this->metadata->addPropertyConstraint('reference', new Valid());
- $this->metadata->setGroupSequence(array($this->metadata->getDefaultGroup()));
-
- $referenceMetadata = new ClassMetadata(get_class($entity->reference));
- $referenceMetadata->addConstraint(new FailingConstraint(array(
- // this constraint is only evaluated if group "Default" is
- // propagated to the reference
- 'groups' => 'Default',
- )));
- $this->metadataFactory->addMetadata($referenceMetadata);
-
- $this->visitor->validate($entity, 'Default', '');
-
- // The validation of the reference's FailingConstraint in group
- // "Default" was launched
- $violations = new ConstraintViolationList(array(
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- 'reference',
- $entity->reference
- ),
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- public function testValidateInOtherGroupTraversesNoGroupSequence()
- {
- $entity = new Entity();
-
- $this->metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
- 'groups' => 'First',
- )));
- $this->metadata->addGetterConstraint('lastName', new FailingConstraint(array(
- 'groups' => $this->metadata->getDefaultGroup(),
- )));
- $this->metadata->setGroupSequence(array('First', $this->metadata->getDefaultGroup()));
-
- $this->visitor->validate($entity, $this->metadata->getDefaultGroup(), '');
-
- // Only group "Second" was validated
- $violations = new ConstraintViolationList(array(
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- 'lastName',
- ''
- ),
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- public function testValidateCascadedPropertyValidatesReferences()
- {
- $entity = new Entity();
- $entity->reference = new Entity();
-
- // add a constraint for the entity that always fails
- $this->metadata->addConstraint(new FailingConstraint());
-
- // validate entity when validating the property "reference"
- $this->metadata->addPropertyConstraint('reference', new Valid());
-
- // invoke validation on an object
- $this->visitor->validate($entity, 'Default', '');
-
- $violations = new ConstraintViolationList(array(
- // generated by the root object
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- '',
- $entity
- ),
- // generated by the reference
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- 'reference',
- $entity->reference
- ),
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- public function testValidateCascadedPropertyValidatesArraysByDefault()
- {
- $entity = new Entity();
- $entity->reference = array('key' => new Entity());
-
- // add a constraint for the entity that always fails
- $this->metadata->addConstraint(new FailingConstraint());
-
- // validate array when validating the property "reference"
- $this->metadata->addPropertyConstraint('reference', new Valid());
-
- $this->visitor->validate($entity, 'Default', '');
-
- $violations = new ConstraintViolationList(array(
- // generated by the root object
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- '',
- $entity
- ),
- // generated by the reference
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- 'reference[key]',
- $entity->reference['key']
- ),
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- public function testValidateCascadedPropertyValidatesTraversableByDefault()
- {
- $entity = new Entity();
- $entity->reference = new \ArrayIterator(array('key' => new Entity()));
-
- // add a constraint for the entity that always fails
- $this->metadata->addConstraint(new FailingConstraint());
-
- // validate array when validating the property "reference"
- $this->metadata->addPropertyConstraint('reference', new Valid());
-
- $this->visitor->validate($entity, 'Default', '');
-
- $violations = new ConstraintViolationList(array(
- // generated by the root object
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- '',
- $entity
- ),
- // generated by the reference
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- 'reference[key]',
- $entity->reference['key']
- ),
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- public function testValidateCascadedPropertyDoesNotValidateTraversableIfDisabled()
- {
- $entity = new Entity();
- $entity->reference = new \ArrayIterator(array('key' => new Entity()));
-
- $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
-
- // add a constraint for the entity that always fails
- $this->metadata->addConstraint(new FailingConstraint());
-
- // validate array when validating the property "reference"
- $this->metadata->addPropertyConstraint('reference', new Valid(array(
- 'traverse' => false,
- )));
-
- $this->visitor->validate($entity, 'Default', '');
-
- $violations = new ConstraintViolationList(array(
- // generated by the root object
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- '',
- $entity
- ),
- // nothing generated by the reference!
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- public function testMetadataMayNotExistIfTraversalIsEnabled()
- {
- $entity = new Entity();
- $entity->reference = new \ArrayIterator();
-
- $this->metadata->addPropertyConstraint('reference', new Valid(array(
- 'traverse' => true,
- )));
-
- $this->visitor->validate($entity, 'Default', '');
- }
-
- /**
- * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
- */
- public function testMetadataMustExistIfTraversalIsDisabled()
- {
- $entity = new Entity();
- $entity->reference = new \ArrayIterator();
-
- $this->metadata->addPropertyConstraint('reference', new Valid(array(
- 'traverse' => false,
- )));
-
- $this->visitor->validate($entity, 'Default', '');
- }
-
- public function testValidateCascadedPropertyDoesNotRecurseByDefault()
- {
- $entity = new Entity();
- $entity->reference = new \ArrayIterator(array(
- // The inner iterator should not be traversed by default
- 'key' => new \ArrayIterator(array(
- 'nested' => new Entity(),
- )),
- ));
-
- $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
-
- // add a constraint for the entity that always fails
- $this->metadata->addConstraint(new FailingConstraint());
-
- // validate iterator when validating the property "reference"
- $this->metadata->addPropertyConstraint('reference', new Valid());
-
- $this->visitor->validate($entity, 'Default', '');
-
- $violations = new ConstraintViolationList(array(
- // generated by the root object
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- '',
- $entity
- ),
- // nothing generated by the reference!
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- // https://github.com/symfony/symfony/issues/6246
- public function testValidateCascadedPropertyRecursesArraysByDefault()
- {
- $entity = new Entity();
- $entity->reference = array(
- 'key' => array(
- 'nested' => new Entity(),
- ),
- );
-
- // add a constraint for the entity that always fails
- $this->metadata->addConstraint(new FailingConstraint());
-
- // validate iterator when validating the property "reference"
- $this->metadata->addPropertyConstraint('reference', new Valid());
-
- $this->visitor->validate($entity, 'Default', '');
-
- $violations = new ConstraintViolationList(array(
- // generated by the root object
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- '',
- $entity
- ),
- // nothing generated by the reference!
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- 'reference[key][nested]',
- $entity->reference['key']['nested']
- ),
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- public function testValidateCascadedPropertyRecursesIfDeepIsSet()
- {
- $entity = new Entity();
- $entity->reference = new \ArrayIterator(array(
- // The inner iterator should now be traversed
- 'key' => new \ArrayIterator(array(
- 'nested' => new Entity(),
- )),
- ));
-
- // add a constraint for the entity that always fails
- $this->metadata->addConstraint(new FailingConstraint());
-
- // validate iterator when validating the property "reference"
- $this->metadata->addPropertyConstraint('reference', new Valid(array(
- 'deep' => true,
- )));
-
- $this->visitor->validate($entity, 'Default', '');
-
- $violations = new ConstraintViolationList(array(
- // generated by the root object
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- '',
- $entity
- ),
- // nothing generated by the reference!
- new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Root',
- 'reference[key][nested]',
- $entity->reference['key']['nested']
- ),
- ));
-
- $this->assertEquals($violations, $this->visitor->getViolations());
- }
-
- public function testValidateCascadedPropertyDoesNotValidateNestedScalarValues()
- {
- $entity = new Entity();
- $entity->reference = array('scalar', 'values');
-
- // validate array when validating the property "reference"
- $this->metadata->addPropertyConstraint('reference', new Valid());
-
- $this->visitor->validate($entity, 'Default', '');
-
- $this->assertCount(0, $this->visitor->getViolations());
- }
-
- public function testValidateCascadedPropertyDoesNotValidateNullValues()
- {
- $entity = new Entity();
- $entity->reference = null;
-
- $this->metadata->addPropertyConstraint('reference', new Valid());
-
- $this->visitor->validate($entity, 'Default', '');
-
- $this->assertCount(0, $this->visitor->getViolations());
- }
-
- /**
- * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
- */
- public function testValidateCascadedPropertyRequiresObjectOrArray()
- {
- $entity = new Entity();
- $entity->reference = 'no object';
-
- $this->metadata->addPropertyConstraint('reference', new Valid());
-
- $this->visitor->validate($entity, 'Default', '');
- }
-}
diff --git a/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php
new file mode 100644
index 0000000000000..0b011a4792984
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Validator/Abstract2Dot5ApiTest.php
@@ -0,0 +1,681 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Validator;
+
+use Symfony\Component\Validator\Constraints\Callback;
+use Symfony\Component\Validator\Constraints\GroupSequence;
+use Symfony\Component\Validator\Constraints\Traverse;
+use Symfony\Component\Validator\Constraints\Valid;
+use Symfony\Component\Validator\ConstraintViolationInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Tests\Fixtures\Entity;
+use Symfony\Component\Validator\Tests\Fixtures\FakeClassMetadata;
+use Symfony\Component\Validator\Tests\Fixtures\Reference;
+use Symfony\Component\Validator\Validator\ValidatorInterface;
+
+/**
+ * Verifies that a validator satisfies the API of Symfony 2.5+.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest
+{
+ /**
+ * @var ValidatorInterface
+ */
+ protected $validator;
+
+ /**
+ * @param MetadataFactoryInterface $metadataFactory
+ *
+ * @return ValidatorInterface
+ */
+ abstract protected function createValidator(MetadataFactoryInterface $metadataFactory);
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->validator = $this->createValidator($this->metadataFactory);
+ }
+
+ protected function validate($value, $constraints = null, $groups = null)
+ {
+ return $this->validator->validate($value, $constraints, $groups);
+ }
+
+ protected function validateProperty($object, $propertyName, $groups = null)
+ {
+ return $this->validator->validateProperty($object, $propertyName, $groups);
+ }
+
+ protected function validatePropertyValue($object, $propertyName, $value, $groups = null)
+ {
+ return $this->validator->validatePropertyValue($object, $propertyName, $value, $groups);
+ }
+
+ public function testGroupSequenceAbortsAfterFailedGroup()
+ {
+ $entity = new Entity();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message 1');
+ };
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message 2');
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => function () {},
+ 'groups' => 'Group 1',
+ )));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group 2',
+ )));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group 3',
+ )));
+
+ $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3'));
+ $violations = $this->validator->validate($entity, new Valid(), $sequence);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message 1', $violations[0]->getMessage());
+ }
+
+ public function testGroupSequenceIncludesReferences()
+ {
+ $entity = new Entity();
+ $entity->reference = new Reference();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Reference violation 1');
+ };
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Reference violation 2');
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group 1',
+ )));
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group 2',
+ )));
+
+ $sequence = new GroupSequence(array('Group 1', 'Entity'));
+ $violations = $this->validator->validate($entity, new Valid(), $sequence);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Reference violation 1', $violations[0]->getMessage());
+ }
+
+ public function testValidateInSeparateContext()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new Reference();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $violations = $context
+ ->getValidator()
+ // Since the validator is not context aware, the group must
+ // be passed explicitly
+ ->validate($value->reference, new Valid(), 'Group')
+ ;
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $test->assertCount(1, $violations);
+ $test->assertSame('Message value', $violations[0]->getMessage());
+ $test->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $test->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $test->assertSame('', $violations[0]->getPropertyPath());
+ // The root is different as we're in a new context
+ $test->assertSame($entity->reference, $violations[0]->getRoot());
+ $test->assertSame($entity->reference, $violations[0]->getInvalidValue());
+ $test->assertNull($violations[0]->getMessagePluralization());
+ $test->assertNull($violations[0]->getCode());
+
+ // Verify that this method is called
+ $context->addViolation('Separate violation');
+ };
+
+ $callback2 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($entity->reference, $context->getRoot());
+ $test->assertSame($entity->reference, $context->getValue());
+ $test->assertSame($entity->reference, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group',
+ )));
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validator->validate($entity, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $test->assertSame('Separate violation', $violations[0]->getMessage());
+ }
+
+ public function testValidateInContext()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new Reference();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context
+ ->getValidator()
+ ->inContext($context)
+ ->atPath('subpath')
+ ->validate($value->reference)
+ ;
+ };
+
+ $callback2 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('subpath', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity->reference, $context->getValue());
+ $test->assertSame($entity->reference, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group',
+ )));
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validator->validate($entity, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('subpath', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testValidateArrayInContext()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new Reference();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context
+ ->getValidator()
+ ->inContext($context)
+ ->atPath('subpath')
+ ->validate(array('key' => $value->reference))
+ ;
+ };
+
+ $callback2 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('subpath[key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity->reference, $context->getValue());
+ $test->assertSame($entity->reference, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group',
+ )));
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validator->validate($entity, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('subpath[key]', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testTraverseTraversableByDefault()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $traversable = new \ArrayIterator(array('key' => $entity));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, $traversable) {
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('[key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->metadata, $context->getMetadata());
+ $test->assertSame($traversable, $context->getRoot());
+ $test->assertSame($entity, $context->getValue());
+ $test->assertSame($entity, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($traversable, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('[key]', $violations[0]->getPropertyPath());
+ $this->assertSame($traversable, $violations[0]->getRoot());
+ $this->assertSame($entity, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testTraversalEnabledOnClass()
+ {
+ $entity = new Entity();
+ $traversable = new \ArrayIterator(array('key' => $entity));
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message');
+ };
+
+ $traversableMetadata = new ClassMetadata('ArrayIterator');
+ $traversableMetadata->addConstraint(new Traverse(true));
+
+ $this->metadataFactory->addMetadata($traversableMetadata);
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($traversable, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ }
+
+ public function testTraversalDisabledOnClass()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $traversable = new \ArrayIterator(array('key' => $entity));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test) {
+ $test->fail('Should not be called');
+ };
+
+ $traversableMetadata = new ClassMetadata('ArrayIterator');
+ $traversableMetadata->addConstraint(new Traverse(false));
+
+ $this->metadataFactory->addMetadata($traversableMetadata);
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($traversable, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(0, $violations);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\ConstraintDefinitionException
+ */
+ public function testExpectTraversableIfTraversalEnabledOnClass()
+ {
+ $entity = new Entity();
+
+ $this->metadata->addConstraint(new Traverse(true));
+
+ $this->validator->validate($entity);
+ }
+
+ public function testReferenceTraversalDisabledOnClass()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new \ArrayIterator(array('key' => new Reference()));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test) {
+ $test->fail('Should not be called');
+ };
+
+ $traversableMetadata = new ClassMetadata('ArrayIterator');
+ $traversableMetadata->addConstraint(new Traverse(false));
+
+ $this->metadataFactory->addMetadata($traversableMetadata);
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+
+ $violations = $this->validate($entity, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(0, $violations);
+ }
+
+ public function testReferenceTraversalEnabledOnReferenceDisabledOnClass()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new \ArrayIterator(array('key' => new Reference()));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test) {
+ $test->fail('Should not be called');
+ };
+
+ $traversableMetadata = new ClassMetadata('ArrayIterator');
+ $traversableMetadata->addConstraint(new Traverse(false));
+
+ $this->metadataFactory->addMetadata($traversableMetadata);
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+ $this->metadata->addPropertyConstraint('reference', new Valid(array(
+ 'traverse' => true,
+ )));
+
+ $violations = $this->validate($entity, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(0, $violations);
+ }
+
+ public function testReferenceTraversalDisabledOnReferenceEnabledOnClass()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new \ArrayIterator(array('key' => new Reference()));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test) {
+ $test->fail('Should not be called');
+ };
+
+ $traversableMetadata = new ClassMetadata('ArrayIterator');
+ $traversableMetadata->addConstraint(new Traverse(true));
+
+ $this->metadataFactory->addMetadata($traversableMetadata);
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+ $this->metadata->addPropertyConstraint('reference', new Valid(array(
+ 'traverse' => false,
+ )));
+
+ $violations = $this->validate($entity, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(0, $violations);
+ }
+
+ public function testReferenceTraversalRecursionEnabledOnReferenceTraversalEnabledOnClass()
+ {
+ $entity = new Entity();
+ $entity->reference = new \ArrayIterator(array(
+ 2 => new \ArrayIterator(array('key' => new Reference())),
+ ));
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message');
+ };
+
+ $traversableMetadata = new ClassMetadata('ArrayIterator');
+ $traversableMetadata->addConstraint(new Traverse(true));
+
+ $this->metadataFactory->addMetadata($traversableMetadata);
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+ $this->metadata->addPropertyConstraint('reference', new Valid(array(
+ 'deep' => true,
+ )));
+
+ $violations = $this->validate($entity, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ }
+
+ public function testReferenceTraversalRecursionDisabledOnReferenceTraversalEnabledOnClass()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new \ArrayIterator(array(
+ 2 => new \ArrayIterator(array('key' => new Reference())),
+ ));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test) {
+ $test->fail('Should not be called');
+ };
+
+ $traversableMetadata = new ClassMetadata('ArrayIterator');
+ $traversableMetadata->addConstraint(new Traverse(true));
+
+ $this->metadataFactory->addMetadata($traversableMetadata);
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+ $this->metadata->addPropertyConstraint('reference', new Valid(array(
+ 'deep' => false,
+ )));
+
+ $violations = $this->validate($entity, new Valid(), 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(0, $violations);
+ }
+
+ public function testAddCustomizedViolation()
+ {
+ $entity = new Entity();
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->buildViolation('Message %param%')
+ ->setParameter('%param%', 'value')
+ ->setInvalidValue('Invalid value')
+ ->setPlural(2)
+ ->setCode('Code')
+ ->addViolation();
+ };
+
+ $this->metadata->addConstraint(new Callback($callback));
+
+ $violations = $this->validator->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame('Invalid value', $violations[0]->getInvalidValue());
+ $this->assertSame(2, $violations[0]->getMessagePluralization());
+ $this->assertSame('Code', $violations[0]->getCode());
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\UnsupportedMetadataException
+ */
+ public function testMetadataMustImplementClassMetadataInterface()
+ {
+ $entity = new Entity();
+
+ $metadata = $this->getMock('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata');
+ $metadata->expects($this->any())
+ ->method('getClassName')
+ ->will($this->returnValue(get_class($entity)));
+
+ $this->metadataFactory->addMetadata($metadata);
+
+ $this->validator->validate($entity);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\UnsupportedMetadataException
+ */
+ public function testReferenceMetadataMustImplementClassMetadataInterface()
+ {
+ $entity = new Entity();
+ $entity->reference = new Reference();
+
+ $metadata = $this->getMock('Symfony\Component\Validator\Tests\Fixtures\LegacyClassMetadata');
+ $metadata->expects($this->any())
+ ->method('getClassName')
+ ->will($this->returnValue(get_class($entity->reference)));
+
+ $this->metadataFactory->addMetadata($metadata);
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+
+ $this->validator->validate($entity);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\UnsupportedMetadataException
+ */
+ public function testPropertyMetadataMustImplementPropertyMetadataInterface()
+ {
+ $entity = new Entity();
+
+ // Legacy interface
+ $propertyMetadata = $this->getMock('Symfony\Component\Validator\MetadataInterface');
+ $metadata = new FakeClassMetadata(get_class($entity));
+ $metadata->addPropertyMetadata('firstName', $propertyMetadata);
+
+ $this->metadataFactory->addMetadata($metadata);
+
+ $this->validator->validate($entity);
+ }
+
+ public function testNoDuplicateValidationIfClassConstraintInMultipleGroups()
+ {
+ $entity = new Entity();
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message');
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => array('Group 1', 'Group 2'),
+ )));
+
+ $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2'));
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ }
+
+ public function testNoDuplicateValidationIfPropertyConstraintInMultipleGroups()
+ {
+ $entity = new Entity();
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message');
+ };
+
+ $this->metadata->addPropertyConstraint('firstName', new Callback(array(
+ 'callback' => $callback,
+ 'groups' => array('Group 1', 'Group 2'),
+ )));
+
+ $violations = $this->validator->validate($entity, new Valid(), array('Group 1', 'Group 2'));
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\RuntimeException
+ */
+ public function testValidateFailsIfNoConstraintsAndNoObjectOrArray()
+ {
+ $this->validate('Foobar');
+ }
+
+ public function testAccessCurrentObject()
+ {
+ $test = $this;
+ $called = false;
+ $entity = new Entity();
+ $entity->firstName = 'Bernhard';
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, &$called) {
+ $called = true;
+ $test->assertSame($entity, $context->getObject());
+ };
+
+ $this->metadata->addConstraint(new Callback($callback));
+ $this->metadata->addPropertyConstraint('firstName', new Callback($callback));
+
+ $this->validator->validate($entity);
+
+ $this->assertTrue($called);
+ }
+}
diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractLegacyApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractLegacyApiTest.php
new file mode 100644
index 0000000000000..386c250a285e6
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Validator/AbstractLegacyApiTest.php
@@ -0,0 +1,245 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Validator;
+
+use Symfony\Component\Validator\Constraints\Callback;
+use Symfony\Component\Validator\Constraints\Valid;
+use Symfony\Component\Validator\ConstraintViolationInterface;
+use Symfony\Component\Validator\ExecutionContextInterface;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Tests\Fixtures\Entity;
+use Symfony\Component\Validator\Tests\Fixtures\Reference;
+use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface;
+
+/**
+ * Verifies that a validator satisfies the API of Symfony < 2.5.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+abstract class AbstractLegacyApiTest extends AbstractValidatorTest
+{
+ /**
+ * @var LegacyValidatorInterface
+ */
+ protected $validator;
+
+ /**
+ * @param MetadataFactoryInterface $metadataFactory
+ *
+ * @return LegacyValidatorInterface
+ */
+ abstract protected function createValidator(MetadataFactoryInterface $metadataFactory);
+
+ protected function setUp()
+ {
+ parent::setUp();
+
+ $this->validator = $this->createValidator($this->metadataFactory);
+ }
+
+ protected function validate($value, $constraints = null, $groups = null)
+ {
+ if (null === $constraints) {
+ $constraints = new Valid();
+ }
+
+ if ($constraints instanceof Valid) {
+ return $this->validator->validate($value, $groups, $constraints->traverse, $constraints->deep);
+ }
+
+ return $this->validator->validateValue($value, $constraints, $groups);
+ }
+
+ protected function validateProperty($object, $propertyName, $groups = null)
+ {
+ return $this->validator->validateProperty($object, $propertyName, $groups);
+ }
+
+ protected function validatePropertyValue($object, $propertyName, $value, $groups = null)
+ {
+ return $this->validator->validatePropertyValue($object, $propertyName, $value, $groups);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
+ */
+ public function testTraversableTraverseDisabled()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $traversable = new \ArrayIterator(array('key' => $entity));
+
+ $callback = function () use ($test) {
+ $test->fail('Should not be called');
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $this->validator->validate($traversable, 'Group');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
+ */
+ public function testRecursiveTraversableRecursiveTraversalDisabled()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $traversable = new \ArrayIterator(array(
+ 2 => new \ArrayIterator(array('key' => $entity)),
+ ));
+
+ $callback = function () use ($test) {
+ $test->fail('Should not be called');
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $this->validator->validate($traversable, 'Group');
+ }
+
+ public function testValidateInContext()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new Reference();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->validate($value->reference, 'subpath');
+ };
+
+ $callback2 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('subpath', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($test->metadataFactory, $context->getMetadataFactory());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity->reference, $context->getValue());
+ $test->assertSame($entity->reference, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group',
+ )));
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validator->validate($entity, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('subpath', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testValidateArrayInContext()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new Reference();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->validate(array('key' => $value->reference), 'subpath');
+ };
+
+ $callback2 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('subpath[key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($test->metadataFactory, $context->getMetadataFactory());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity->reference, $context->getValue());
+ $test->assertSame($entity->reference, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group',
+ )));
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validator->validate($entity, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('subpath[key]', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testAddCustomizedViolation()
+ {
+ $entity = new Entity();
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation(
+ 'Message %param%',
+ array('%param%' => 'value'),
+ 'Invalid value',
+ 2,
+ 'Code'
+ );
+ };
+
+ $this->metadata->addConstraint(new Callback($callback));
+
+ $violations = $this->validator->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame('Invalid value', $violations[0]->getInvalidValue());
+ $this->assertSame(2, $violations[0]->getMessagePluralization());
+ $this->assertSame('Code', $violations[0]->getCode());
+ }
+
+ public function testGetMetadataFactory()
+ {
+ $this->assertSame($this->metadataFactory, $this->validator->getMetadataFactory());
+ }
+}
diff --git a/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php b/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php
new file mode 100644
index 0000000000000..2808d38137ac6
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Validator/AbstractValidatorTest.php
@@ -0,0 +1,1209 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Validator;
+
+use Symfony\Component\Validator\Constraints\Callback;
+use Symfony\Component\Validator\Constraints\GroupSequence;
+use Symfony\Component\Validator\Constraints\Valid;
+use Symfony\Component\Validator\ConstraintViolationInterface;
+use Symfony\Component\Validator\ExecutionContextInterface;
+use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\Tests\Fixtures\Entity;
+use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
+use Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity;
+use Symfony\Component\Validator\Tests\Fixtures\Reference;
+
+/**
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+abstract class AbstractValidatorTest extends \PHPUnit_Framework_TestCase
+{
+ const ENTITY_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Entity';
+
+ const REFERENCE_CLASS = 'Symfony\Component\Validator\Tests\Fixtures\Reference';
+
+ /**
+ * @var FakeMetadataFactory
+ */
+ public $metadataFactory;
+
+ /**
+ * @var ClassMetadata
+ */
+ public $metadata;
+
+ /**
+ * @var ClassMetadata
+ */
+ public $referenceMetadata;
+
+ protected function setUp()
+ {
+ $this->metadataFactory = new FakeMetadataFactory();
+ $this->metadata = new ClassMetadata(self::ENTITY_CLASS);
+ $this->referenceMetadata = new ClassMetadata(self::REFERENCE_CLASS);
+ $this->metadataFactory->addMetadata($this->metadata);
+ $this->metadataFactory->addMetadata($this->referenceMetadata);
+ }
+
+ protected function tearDown()
+ {
+ $this->metadataFactory = null;
+ $this->metadata = null;
+ $this->referenceMetadata = null;
+ }
+
+ abstract protected function validate($value, $constraints = null, $groups = null);
+
+ abstract protected function validateProperty($object, $propertyName, $groups = null);
+
+ abstract protected function validatePropertyValue($object, $propertyName, $value, $groups = null);
+
+ public function testValidate()
+ {
+ $test = $this;
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test) {
+ $test->assertNull($context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame('Bernhard', $context->getRoot());
+ $test->assertSame('Bernhard', $context->getValue());
+ $test->assertSame('Bernhard', $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $constraint = new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ ));
+
+ $violations = $this->validate('Bernhard', $constraint, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('', $violations[0]->getPropertyPath());
+ $this->assertSame('Bernhard', $violations[0]->getRoot());
+ $this->assertSame('Bernhard', $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testClassConstraint()
+ {
+ $test = $this;
+ $entity = new Entity();
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->metadata, $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity, $context->getValue());
+ $test->assertSame($entity, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testPropertyConstraint()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->firstName = 'Bernhard';
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $propertyMetadatas = $test->metadata->getPropertyMetadata('firstName');
+
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertSame('firstName', $context->getPropertyName());
+ $test->assertSame('firstName', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($propertyMetadatas[0], $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame('Bernhard', $context->getValue());
+ $test->assertSame('Bernhard', $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('firstName', new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('firstName', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame('Bernhard', $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testGetterConstraint()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->setLastName('Schussek');
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $propertyMetadatas = $test->metadata->getPropertyMetadata('lastName');
+
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertSame('lastName', $context->getPropertyName());
+ $test->assertSame('lastName', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($propertyMetadatas[0], $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame('Schussek', $context->getValue());
+ $test->assertSame('Schussek', $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addGetterConstraint('lastName', new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('lastName', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame('Schussek', $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testArray()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $array = array('key' => $entity);
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, $array) {
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('[key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->metadata, $context->getMetadata());
+ $test->assertSame($array, $context->getRoot());
+ $test->assertSame($entity, $context->getValue());
+ $test->assertSame($entity, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($array, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('[key]', $violations[0]->getPropertyPath());
+ $this->assertSame($array, $violations[0]->getRoot());
+ $this->assertSame($entity, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testRecursiveArray()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $array = array(2 => array('key' => $entity));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, $array) {
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('[2][key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->metadata, $context->getMetadata());
+ $test->assertSame($array, $context->getRoot());
+ $test->assertSame($entity, $context->getValue());
+ $test->assertSame($entity, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($array, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('[2][key]', $violations[0]->getPropertyPath());
+ $this->assertSame($array, $violations[0]->getRoot());
+ $this->assertSame($entity, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testTraversable()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $traversable = new \ArrayIterator(array('key' => $entity));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, $traversable) {
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('[key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->metadata, $context->getMetadata());
+ $test->assertSame($traversable, $context->getRoot());
+ $test->assertSame($entity, $context->getValue());
+ $test->assertSame($entity, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($traversable, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('[key]', $violations[0]->getPropertyPath());
+ $this->assertSame($traversable, $violations[0]->getRoot());
+ $this->assertSame($entity, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testRecursiveTraversable()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $traversable = new \ArrayIterator(array(
+ 2 => new \ArrayIterator(array('key' => $entity)),
+ ));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity, $traversable) {
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('[2][key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->metadata, $context->getMetadata());
+ $test->assertSame($traversable, $context->getRoot());
+ $test->assertSame($entity, $context->getValue());
+ $test->assertSame($entity, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($traversable, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('[2][key]', $violations[0]->getPropertyPath());
+ $this->assertSame($traversable, $violations[0]->getRoot());
+ $this->assertSame($entity, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testReferenceClassConstraint()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new Reference();
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('reference', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity->reference, $context->getValue());
+ $test->assertSame($entity->reference, $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('reference', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity->reference, $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testReferencePropertyConstraint()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new Reference();
+ $entity->reference->value = 'Foobar';
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $propertyMetadatas = $test->referenceMetadata->getPropertyMetadata('value');
+
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertSame('value', $context->getPropertyName());
+ $test->assertSame('reference.value', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($propertyMetadatas[0], $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame('Foobar', $context->getValue());
+ $test->assertSame('Foobar', $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->referenceMetadata->addPropertyConstraint('value', new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('reference.value', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame('Foobar', $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testReferenceGetterConstraint()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new Reference();
+ $entity->reference->setPrivateValue('Bamboo');
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $propertyMetadatas = $test->referenceMetadata->getPropertyMetadata('privateValue');
+
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertSame('privateValue', $context->getPropertyName());
+ $test->assertSame('reference.privateValue', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($propertyMetadatas[0], $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame('Bamboo', $context->getValue());
+ $test->assertSame('Bamboo', $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->referenceMetadata->addPropertyConstraint('privateValue', new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('reference.privateValue', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame('Bamboo', $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testsIgnoreNullReference()
+ {
+ $entity = new Entity();
+ $entity->reference = null;
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+
+ $violations = $this->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(0, $violations);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
+ */
+ public function testFailOnScalarReferences()
+ {
+ $entity = new Entity();
+ $entity->reference = 'string';
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+
+ $this->validate($entity);
+ }
+
+ public function testArrayReference()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = array('key' => new Reference());
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('reference[key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity->reference['key'], $context->getValue());
+ $test->assertSame($entity->reference['key'], $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('reference[key]', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity->reference['key'], $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ // https://github.com/symfony/symfony/issues/6246
+ public function testRecursiveArrayReference()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = array(2 => array('key' => new Reference()));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('reference[2][key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity->reference[2]['key'], $context->getValue());
+ $test->assertSame($entity->reference[2]['key'], $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('reference[2][key]', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity->reference[2]['key'], $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testArrayTraversalCannotBeDisabled()
+ {
+ $entity = new Entity();
+ $entity->reference = array('key' => new Reference());
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid(array(
+ 'traverse' => false,
+ )));
+ $this->referenceMetadata->addConstraint(new Callback($callback));
+
+ $violations = $this->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ }
+
+ public function testRecursiveArrayTraversalCannotBeDisabled()
+ {
+ $entity = new Entity();
+ $entity->reference = array(2 => array('key' => new Reference()));
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid(array(
+ 'traverse' => false,
+ )));
+ $this->referenceMetadata->addConstraint(new Callback($callback));
+
+ $violations = $this->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ }
+
+ public function testIgnoreScalarsDuringArrayTraversal()
+ {
+ $entity = new Entity();
+ $entity->reference = array('string', 1234);
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+
+ $violations = $this->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(0, $violations);
+ }
+
+ public function testIgnoreNullDuringArrayTraversal()
+ {
+ $entity = new Entity();
+ $entity->reference = array(null);
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+
+ $violations = $this->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(0, $violations);
+ }
+
+ public function testTraversableReference()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new \ArrayIterator(array('key' => new Reference()));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('reference[key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity->reference['key'], $context->getValue());
+ $test->assertSame($entity->reference['key'], $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('reference[key]', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity->reference['key'], $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testDisableTraversableTraversal()
+ {
+ $entity = new Entity();
+ $entity->reference = new \ArrayIterator(array('key' => new Reference()));
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadataFactory->addMetadata(new ClassMetadata('ArrayIterator'));
+ $this->metadata->addPropertyConstraint('reference', new Valid(array(
+ 'traverse' => false,
+ )));
+ $this->referenceMetadata->addConstraint(new Callback($callback));
+
+ $violations = $this->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(0, $violations);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Validator\Exception\NoSuchMetadataException
+ */
+ public function testMetadataMustExistIfTraversalIsDisabled()
+ {
+ $entity = new Entity();
+ $entity->reference = new \ArrayIterator();
+
+ $this->metadata->addPropertyConstraint('reference', new Valid(array(
+ 'traverse' => false,
+ )));
+
+ $this->validate($entity);
+ }
+
+ public function testEnableRecursiveTraversableTraversal()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->reference = new \ArrayIterator(array(
+ 2 => new \ArrayIterator(array('key' => new Reference())),
+ ));
+
+ $callback = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $test->assertSame($test::REFERENCE_CLASS, $context->getClassName());
+ $test->assertNull($context->getPropertyName());
+ $test->assertSame('reference[2][key]', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($test->referenceMetadata, $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame($entity->reference[2]['key'], $context->getValue());
+ $test->assertSame($entity->reference[2]['key'], $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid(array(
+ 'deep' => true,
+ )));
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('reference[2][key]', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame($entity->reference[2]['key'], $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ public function testValidateProperty()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->firstName = 'Bernhard';
+ $entity->setLastName('Schussek');
+
+ $callback1 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $propertyMetadatas = $test->metadata->getPropertyMetadata('firstName');
+
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertSame('firstName', $context->getPropertyName());
+ $test->assertSame('firstName', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($propertyMetadatas[0], $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame('Bernhard', $context->getValue());
+ $test->assertSame('Bernhard', $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Other violation');
+ };
+
+ $this->metadata->addPropertyConstraint('firstName', new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group',
+ )));
+ $this->metadata->addPropertyConstraint('lastName', new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validateProperty($entity, 'firstName', 'Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('firstName', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame('Bernhard', $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ /**
+ * Cannot be UnsupportedMetadataException for BC with Symfony < 2.5.
+ *
+ * @expectedException \Symfony\Component\Validator\Exception\ValidatorException
+ */
+ public function testValidatePropertyFailsIfPropertiesNotSupported()
+ {
+ // $metadata does not implement PropertyMetadataContainerInterface
+ $metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface');
+
+ $this->metadataFactory->addMetadataForValue('VALUE', $metadata);
+
+ $this->validateProperty('VALUE', 'someProperty');
+ }
+
+ public function testValidatePropertyValue()
+ {
+ $test = $this;
+ $entity = new Entity();
+ $entity->setLastName('Schussek');
+
+ $callback1 = function ($value, ExecutionContextInterface $context) use ($test, $entity) {
+ $propertyMetadatas = $test->metadata->getPropertyMetadata('firstName');
+
+ $test->assertSame($test::ENTITY_CLASS, $context->getClassName());
+ $test->assertSame('firstName', $context->getPropertyName());
+ $test->assertSame('firstName', $context->getPropertyPath());
+ $test->assertSame('Group', $context->getGroup());
+ $test->assertSame($propertyMetadatas[0], $context->getMetadata());
+ $test->assertSame($entity, $context->getRoot());
+ $test->assertSame('Bernhard', $context->getValue());
+ $test->assertSame('Bernhard', $value);
+
+ $context->addViolation('Message %param%', array('%param%' => 'value'));
+ };
+
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Other violation');
+ };
+
+ $this->metadata->addPropertyConstraint('firstName', new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group',
+ )));
+ $this->metadata->addPropertyConstraint('lastName', new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group',
+ )));
+
+ $violations = $this->validatePropertyValue(
+ $entity,
+ 'firstName',
+ 'Bernhard',
+ 'Group'
+ );
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Message value', $violations[0]->getMessage());
+ $this->assertSame('Message %param%', $violations[0]->getMessageTemplate());
+ $this->assertSame(array('%param%' => 'value'), $violations[0]->getMessageParameters());
+ $this->assertSame('firstName', $violations[0]->getPropertyPath());
+ $this->assertSame($entity, $violations[0]->getRoot());
+ $this->assertSame('Bernhard', $violations[0]->getInvalidValue());
+ $this->assertNull($violations[0]->getMessagePluralization());
+ $this->assertNull($violations[0]->getCode());
+ }
+
+ /**
+ * Cannot be UnsupportedMetadataException for BC with Symfony < 2.5.
+ *
+ * @expectedException \Symfony\Component\Validator\Exception\ValidatorException
+ */
+ public function testValidatePropertyValueFailsIfPropertiesNotSupported()
+ {
+ // $metadata does not implement PropertyMetadataContainerInterface
+ $metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface');
+
+ $this->metadataFactory->addMetadataForValue('VALUE', $metadata);
+
+ $this->validatePropertyValue('VALUE', 'someProperty', 'someValue');
+ }
+
+ public function testValidateObjectOnlyOncePerGroup()
+ {
+ $entity = new Entity();
+ $entity->reference = new Reference();
+ $entity->reference2 = $entity->reference;
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message');
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->metadata->addPropertyConstraint('reference2', new Valid());
+ $this->referenceMetadata->addConstraint(new Callback($callback));
+
+ $violations = $this->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ }
+
+ public function testValidateDifferentObjectsSeparately()
+ {
+ $entity = new Entity();
+ $entity->reference = new Reference();
+ $entity->reference2 = new Reference();
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message');
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->metadata->addPropertyConstraint('reference2', new Valid());
+ $this->referenceMetadata->addConstraint(new Callback($callback));
+
+ $violations = $this->validate($entity);
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(2, $violations);
+ }
+
+ public function testValidateSingleGroup()
+ {
+ $entity = new Entity();
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message');
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group 1',
+ )));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group 2',
+ )));
+
+ $violations = $this->validate($entity, null, 'Group 2');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ }
+
+ public function testValidateMultipleGroups()
+ {
+ $entity = new Entity();
+
+ $callback = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Message');
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group 1',
+ )));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback,
+ 'groups' => 'Group 2',
+ )));
+
+ $violations = $this->validate($entity, null, array('Group 1', 'Group 2'));
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(2, $violations);
+ }
+
+ public function testReplaceDefaultGroupByGroupSequenceObject()
+ {
+ $entity = new Entity();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in Group 2');
+ };
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in Group 3');
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => function () {},
+ 'groups' => 'Group 1',
+ )));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group 2',
+ )));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group 3',
+ )));
+
+ $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3', 'Entity'));
+ $this->metadata->setGroupSequence($sequence);
+
+ $violations = $this->validate($entity, null, 'Default');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Violation in Group 2', $violations[0]->getMessage());
+ }
+
+ public function testReplaceDefaultGroupByGroupSequenceArray()
+ {
+ $entity = new Entity();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in Group 2');
+ };
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in Group 3');
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => function () {},
+ 'groups' => 'Group 1',
+ )));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group 2',
+ )));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group 3',
+ )));
+
+ $sequence = array('Group 1', 'Group 2', 'Group 3', 'Entity');
+ $this->metadata->setGroupSequence($sequence);
+
+ $violations = $this->validate($entity, null, 'Default');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Violation in Group 2', $violations[0]->getMessage());
+ }
+
+ public function testPropagateDefaultGroupToReferenceWhenReplacingDefaultGroup()
+ {
+ $entity = new Entity();
+ $entity->reference = new Reference();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in Default group');
+ };
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in group sequence');
+ };
+
+ $this->metadata->addPropertyConstraint('reference', new Valid());
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Default',
+ )));
+ $this->referenceMetadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group 1',
+ )));
+
+ $sequence = new GroupSequence(array('Group 1', 'Entity'));
+ $this->metadata->setGroupSequence($sequence);
+
+ $violations = $this->validate($entity, null, 'Default');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Violation in Default group', $violations[0]->getMessage());
+ }
+
+ public function testValidateCustomGroupWhenDefaultGroupWasReplaced()
+ {
+ $entity = new Entity();
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in other group');
+ };
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in group sequence');
+ };
+
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Other Group',
+ )));
+ $this->metadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group 1',
+ )));
+
+ $sequence = new GroupSequence(array('Group 1', 'Entity'));
+ $this->metadata->setGroupSequence($sequence);
+
+ $violations = $this->validate($entity, null, 'Other Group');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Violation in other group', $violations[0]->getMessage());
+ }
+
+ public function testReplaceDefaultGroupWithObjectFromGroupSequenceProvider()
+ {
+ $sequence = new GroupSequence(array('Group 1', 'Group 2', 'Group 3', 'Entity'));
+ $entity = new GroupSequenceProviderEntity($sequence);
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in Group 2');
+ };
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in Group 3');
+ };
+
+ $metadata = new ClassMetadata(get_class($entity));
+ $metadata->addConstraint(new Callback(array(
+ 'callback' => function () {},
+ 'groups' => 'Group 1',
+ )));
+ $metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group 2',
+ )));
+ $metadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group 3',
+ )));
+ $metadata->setGroupSequenceProvider(true);
+
+ $this->metadataFactory->addMetadata($metadata);
+
+ $violations = $this->validate($entity, null, 'Default');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Violation in Group 2', $violations[0]->getMessage());
+ }
+
+ public function testReplaceDefaultGroupWithArrayFromGroupSequenceProvider()
+ {
+ $sequence = array('Group 1', 'Group 2', 'Group 3', 'Entity');
+ $entity = new GroupSequenceProviderEntity($sequence);
+
+ $callback1 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in Group 2');
+ };
+ $callback2 = function ($value, ExecutionContextInterface $context) {
+ $context->addViolation('Violation in Group 3');
+ };
+
+ $metadata = new ClassMetadata(get_class($entity));
+ $metadata->addConstraint(new Callback(array(
+ 'callback' => function () {},
+ 'groups' => 'Group 1',
+ )));
+ $metadata->addConstraint(new Callback(array(
+ 'callback' => $callback1,
+ 'groups' => 'Group 2',
+ )));
+ $metadata->addConstraint(new Callback(array(
+ 'callback' => $callback2,
+ 'groups' => 'Group 3',
+ )));
+ $metadata->setGroupSequenceProvider(true);
+
+ $this->metadataFactory->addMetadata($metadata);
+
+ $violations = $this->validate($entity, null, 'Default');
+
+ /** @var ConstraintViolationInterface[] $violations */
+ $this->assertCount(1, $violations);
+ $this->assertSame('Violation in Group 2', $violations[0]->getMessage());
+ }
+}
diff --git a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php
new file mode 100644
index 0000000000000..7faeea6284e9c
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidator2Dot5ApiTest.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Validator;
+
+use Symfony\Component\Validator\ConstraintValidatorFactory;
+use Symfony\Component\Validator\Context\LegacyExecutionContextFactory;
+use Symfony\Component\Validator\DefaultTranslator;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Validator\LegacyValidator;
+
+class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
+{
+ protected function setUp()
+ {
+ if (version_compare(PHP_VERSION, '5.3.9', '<')) {
+ $this->markTestSkipped('Not supported prior to PHP 5.3.9');
+ }
+
+ parent::setUp();
+ }
+
+ protected function createValidator(MetadataFactoryInterface $metadataFactory)
+ {
+ $contextFactory = new LegacyExecutionContextFactory($metadataFactory, new DefaultTranslator());
+
+ return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory());
+ }
+}
diff --git a/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php
new file mode 100644
index 0000000000000..581e6768399aa
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Validator/LegacyValidatorLegacyApiTest.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Validator;
+
+use Symfony\Component\Validator\ConstraintValidatorFactory;
+use Symfony\Component\Validator\Context\LegacyExecutionContextFactory;
+use Symfony\Component\Validator\DefaultTranslator;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Validator\LegacyValidator;
+
+class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest
+{
+ protected function setUp()
+ {
+ if (version_compare(PHP_VERSION, '5.3.9', '<')) {
+ $this->markTestSkipped('Not supported prior to PHP 5.3.9');
+ }
+
+ parent::setUp();
+ }
+
+ protected function createValidator(MetadataFactoryInterface $metadataFactory)
+ {
+ $contextFactory = new LegacyExecutionContextFactory($metadataFactory, new DefaultTranslator());
+
+ return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory());
+ }
+}
diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php
new file mode 100644
index 0000000000000..da43279638852
--- /dev/null
+++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Tests\Validator;
+
+use Symfony\Component\Validator\ConstraintValidatorFactory;
+use Symfony\Component\Validator\Context\ExecutionContextFactory;
+use Symfony\Component\Validator\DefaultTranslator;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Validator\RecursiveValidator;
+
+class RecursiveValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
+{
+ protected function createValidator(MetadataFactoryInterface $metadataFactory)
+ {
+ $contextFactory = new ExecutionContextFactory(new DefaultTranslator());
+
+ return new RecursiveValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory());
+ }
+}
diff --git a/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php b/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php
index 900243f31ad65..ed914d1394904 100644
--- a/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php
+++ b/src/Symfony/Component/Validator/Tests/ValidatorBuilderTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\Validator\Tests;
+use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\ValidatorBuilder;
use Symfony\Component\Validator\ValidatorBuilderInterface;
@@ -108,4 +109,37 @@ public function testSetTranslationDomain()
{
$this->assertSame($this->builder, $this->builder->setTranslationDomain('TRANS_DOMAIN'));
}
+
+ public function testDefaultApiVersion()
+ {
+ if (version_compare(PHP_VERSION, '5.3.9', '<')) {
+ // Old implementation on PHP < 5.3.9
+ $this->assertInstanceOf('Symfony\Component\Validator\Validator', $this->builder->getValidator());
+ } else {
+ // Legacy compatible implementation on PHP >= 5.3.9
+ $this->assertInstanceOf('Symfony\Component\Validator\Validator\LegacyValidator', $this->builder->getValidator());
+ }
+ }
+
+ public function testSetApiVersion24()
+ {
+ $this->assertSame($this->builder, $this->builder->setApiVersion(Validation::API_VERSION_2_4));
+ $this->assertInstanceOf('Symfony\Component\Validator\Validator', $this->builder->getValidator());
+ }
+
+ public function testSetApiVersion25()
+ {
+ $this->assertSame($this->builder, $this->builder->setApiVersion(Validation::API_VERSION_2_5));
+ $this->assertInstanceOf('Symfony\Component\Validator\Validator\RecursiveValidator', $this->builder->getValidator());
+ }
+
+ public function testSetApiVersion24And25()
+ {
+ if (version_compare(PHP_VERSION, '5.3.9', '<')) {
+ $this->markTestSkipped('Not supported prior to PHP 5.3.9');
+ }
+
+ $this->assertSame($this->builder, $this->builder->setApiVersion(Validation::API_VERSION_2_5_BC));
+ $this->assertInstanceOf('Symfony\Component\Validator\Validator\LegacyValidator', $this->builder->getValidator());
+ }
}
diff --git a/src/Symfony/Component/Validator/Tests/ValidatorTest.php b/src/Symfony/Component/Validator/Tests/ValidatorTest.php
index 85a61e4816da8..0f588fef8d3db 100644
--- a/src/Symfony/Component/Validator/Tests/ValidatorTest.php
+++ b/src/Symfony/Component/Validator/Tests/ValidatorTest.php
@@ -11,213 +11,19 @@
namespace Symfony\Component\Validator\Tests;
-use Symfony\Component\Validator\Tests\Fixtures\Entity;
-use Symfony\Component\Validator\Tests\Fixtures\GroupSequenceProviderEntity;
-use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
-use Symfony\Component\Validator\Tests\Fixtures\FailingConstraint;
-use Symfony\Component\Validator\Validator;
-use Symfony\Component\Validator\DefaultTranslator;
-use Symfony\Component\Validator\ConstraintViolation;
-use Symfony\Component\Validator\ConstraintViolationList;
-use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Constraints\Valid;
-use Symfony\Component\Validator\Mapping\ClassMetadata;
+use Symfony\Component\Validator\ConstraintValidatorFactory;
+use Symfony\Component\Validator\DefaultTranslator;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Tests\Fixtures\Entity;
+use Symfony\Component\Validator\Tests\Validator\AbstractLegacyApiTest;
+use Symfony\Component\Validator\Validator as LegacyValidator;
-class ValidatorTest extends \PHPUnit_Framework_TestCase
+class ValidatorTest extends AbstractLegacyApiTest
{
- /**
- * @var FakeMetadataFactory
- */
- private $metadataFactory;
-
- /**
- * @var Validator
- */
- private $validator;
-
- protected function setUp()
- {
- $this->metadataFactory = new FakeMetadataFactory();
- $this->validator = new Validator($this->metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator());
- }
-
- protected function tearDown()
+ protected function createValidator(MetadataFactoryInterface $metadataFactory)
{
- $this->metadataFactory = null;
- $this->validator = null;
- }
-
- public function testValidateDefaultGroup()
- {
- $entity = new Entity();
- $metadata = new ClassMetadata(get_class($entity));
- $metadata->addPropertyConstraint('firstName', new FailingConstraint());
- $metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
- 'groups' => 'Custom',
- )));
- $this->metadataFactory->addMetadata($metadata);
-
- // Only the constraint of group "Default" failed
- $violations = new ConstraintViolationList();
- $violations->add(new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- $entity,
- 'firstName',
- ''
- ));
-
- $this->assertEquals($violations, $this->validator->validate($entity));
- }
-
- public function testValidateOneGroup()
- {
- $entity = new Entity();
- $metadata = new ClassMetadata(get_class($entity));
- $metadata->addPropertyConstraint('firstName', new FailingConstraint());
- $metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
- 'groups' => 'Custom',
- )));
- $this->metadataFactory->addMetadata($metadata);
-
- // Only the constraint of group "Custom" failed
- $violations = new ConstraintViolationList();
- $violations->add(new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- $entity,
- 'lastName',
- ''
- ));
-
- $this->assertEquals($violations, $this->validator->validate($entity, 'Custom'));
- }
-
- public function testValidateMultipleGroups()
- {
- $entity = new Entity();
- $metadata = new ClassMetadata(get_class($entity));
- $metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
- 'groups' => 'First',
- )));
- $metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
- 'groups' => 'Second',
- )));
- $this->metadataFactory->addMetadata($metadata);
-
- // The constraints of both groups failed
- $violations = new ConstraintViolationList();
- $violations->add(new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- $entity,
- 'firstName',
- ''
- ));
- $violations->add(new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- $entity,
- 'lastName',
- ''
- ));
-
- $result = $this->validator->validate($entity, array('First', 'Second'));
-
- $this->assertEquals($violations, $result);
- }
-
- public function testValidateGroupSequenceProvider()
- {
- $entity = new GroupSequenceProviderEntity();
- $metadata = new ClassMetadata(get_class($entity));
- $metadata->addPropertyConstraint('firstName', new FailingConstraint(array(
- 'groups' => 'First',
- )));
- $metadata->addPropertyConstraint('lastName', new FailingConstraint(array(
- 'groups' => 'Second',
- )));
- $metadata->setGroupSequenceProvider(true);
- $this->metadataFactory->addMetadata($metadata);
-
- $violations = new ConstraintViolationList();
- $violations->add(new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- $entity,
- 'firstName',
- ''
- ));
-
- $entity->setGroups(array('First'));
- $result = $this->validator->validate($entity);
- $this->assertEquals($violations, $result);
-
- $violations = new ConstraintViolationList();
- $violations->add(new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- $entity,
- 'lastName',
- ''
- ));
-
- $entity->setGroups(array('Second'));
- $result = $this->validator->validate($entity);
- $this->assertEquals($violations, $result);
-
- $entity->setGroups(array());
- $result = $this->validator->validate($entity);
- $this->assertEquals(new ConstraintViolationList(), $result);
- }
-
- public function testValidateProperty()
- {
- $entity = new Entity();
- $metadata = new ClassMetadata(get_class($entity));
- $metadata->addPropertyConstraint('firstName', new FailingConstraint());
- $this->metadataFactory->addMetadata($metadata);
-
- $result = $this->validator->validateProperty($entity, 'firstName');
-
- $this->assertCount(1, $result);
-
- $result = $this->validator->validateProperty($entity, 'lastName');
-
- $this->assertCount(0, $result);
- }
-
- public function testValidatePropertyValue()
- {
- $entity = new Entity();
- $metadata = new ClassMetadata(get_class($entity));
- $metadata->addPropertyConstraint('firstName', new FailingConstraint());
- $this->metadataFactory->addMetadata($metadata);
-
- $result = $this->validator->validatePropertyValue(get_class($entity), 'firstName', 'Bernhard');
-
- $this->assertCount(1, $result);
- }
-
- public function testValidateValue()
- {
- $violations = new ConstraintViolationList();
- $violations->add(new ConstraintViolation(
- 'Failed',
- 'Failed',
- array(),
- 'Bernhard',
- '',
- 'Bernhard'
- ));
-
- $this->assertEquals($violations, $this->validator->validateValue('Bernhard', new FailingConstraint()));
+ return new LegacyValidator($metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator());
}
/**
@@ -225,52 +31,6 @@ public function testValidateValue()
*/
public function testValidateValueRejectsValid()
{
- $entity = new Entity();
- $metadata = new ClassMetadata(get_class($entity));
- $this->metadataFactory->addMetadata($metadata);
-
- $this->validator->validateValue($entity, new Valid());
- }
-
- /**
- * @expectedException \Symfony\Component\Validator\Exception\ValidatorException
- */
- public function testValidatePropertyFailsIfPropertiesNotSupported()
- {
- // $metadata does not implement PropertyMetadataContainerInterface
- $metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface');
- $this->metadataFactory = $this->getMock('Symfony\Component\Validator\MetadataFactoryInterface');
- $this->metadataFactory->expects($this->any())
- ->method('getMetadataFor')
- ->with('VALUE')
- ->will($this->returnValue($metadata));
- $this->validator = new Validator($this->metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator());
-
- $this->validator->validateProperty('VALUE', 'someProperty');
- }
-
- /**
- * @expectedException \Symfony\Component\Validator\Exception\ValidatorException
- */
- public function testValidatePropertyValueFailsIfPropertiesNotSupported()
- {
- // $metadata does not implement PropertyMetadataContainerInterface
- $metadata = $this->getMock('Symfony\Component\Validator\MetadataInterface');
- $this->metadataFactory = $this->getMock('Symfony\Component\Validator\MetadataFactoryInterface');
- $this->metadataFactory->expects($this->any())
- ->method('getMetadataFor')
- ->with('VALUE')
- ->will($this->returnValue($metadata));
- $this->validator = new Validator($this->metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator());
-
- $this->validator->validatePropertyValue('VALUE', 'someProperty', 'propertyValue');
- }
-
- public function testGetMetadataFactory()
- {
- $this->assertInstanceOf(
- 'Symfony\Component\Validator\MetadataFactoryInterface',
- $this->validator->getMetadataFactory()
- );
+ $this->validator->validateValue(new Entity(), new Valid());
}
}
diff --git a/src/Symfony/Component/Validator/Util/PropertyPath.php b/src/Symfony/Component/Validator/Util/PropertyPath.php
new file mode 100644
index 0000000000000..c8f20e7200c26
--- /dev/null
+++ b/src/Symfony/Component/Validator/Util/PropertyPath.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Util;
+
+/**
+ * Contains utility methods for dealing with property paths.
+ *
+ * For more extensive functionality, use Symfony's PropertyAccess component.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+class PropertyPath
+{
+ /**
+ * Appends a path to a given property path.
+ *
+ * If the base path is empty, the appended path will be returned unchanged.
+ * If the base path is not empty, and the appended path starts with a
+ * squared opening bracket ("["), the concatenation of the two paths is
+ * returned. Otherwise, the concatenation of the two paths is returned,
+ * separated by a dot (".").
+ *
+ * @param string $basePath The base path
+ * @param string $subPath The path to append
+ *
+ * @return string The concatenation of the two property paths
+ */
+ public static function append($basePath, $subPath)
+ {
+ if ('' !== (string) $subPath) {
+ if ('[' === $subPath{1}) {
+ return $basePath.$subPath;
+ }
+
+ return $basePath ? $basePath.'.'.$subPath : $subPath;
+ }
+
+ return $basePath;
+ }
+
+ /**
+ * Not instantiable.
+ */
+ private function __construct()
+ {
+ }
+}
diff --git a/src/Symfony/Component/Validator/Validation.php b/src/Symfony/Component/Validator/Validation.php
index de77e838fb6ff..b304dbb935bb9 100644
--- a/src/Symfony/Component/Validator/Validation.php
+++ b/src/Symfony/Component/Validator/Validation.php
@@ -18,6 +18,22 @@
*/
final class Validation
{
+ /**
+ * The Validator API provided by Symfony 2.4 and older.
+ */
+ const API_VERSION_2_4 = 1;
+
+ /**
+ * The Validator API provided by Symfony 2.5 and newer.
+ */
+ const API_VERSION_2_5 = 2;
+
+ /**
+ * The Validator API provided by Symfony 2.5 and newer with a backwards
+ * compatibility layer for 2.4 and older.
+ */
+ const API_VERSION_2_5_BC = 3;
+
/**
* Creates a new validator.
*
diff --git a/src/Symfony/Component/Validator/ValidationVisitor.php b/src/Symfony/Component/Validator/ValidationVisitor.php
index ddff8adc60382..fef687491f4b9 100644
--- a/src/Symfony/Component/Validator/ValidationVisitor.php
+++ b/src/Symfony/Component/Validator/ValidationVisitor.php
@@ -11,15 +11,18 @@
namespace Symfony\Component\Validator;
+use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Exception\NoSuchMetadataException;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
-use Symfony\Component\Translation\TranslatorInterface;
/**
* Default implementation of {@link ValidationVisitorInterface} and
* {@link GlobalExecutionContextInterface}.
*
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link NodeVisitor\NodeVisitorInterface} instead.
*/
class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionContextInterface
{
diff --git a/src/Symfony/Component/Validator/ValidationVisitorInterface.php b/src/Symfony/Component/Validator/ValidationVisitorInterface.php
index e4163718b30d0..513775b31f1cc 100644
--- a/src/Symfony/Component/Validator/ValidationVisitorInterface.php
+++ b/src/Symfony/Component/Validator/ValidationVisitorInterface.php
@@ -33,6 +33,9 @@
*
*
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link NodeVisitor\NodeVisitorInterface} instead.
*/
interface ValidationVisitorInterface
{
@@ -62,6 +65,8 @@ interface ValidationVisitorInterface
*
* @throws Exception\NoSuchMetadataException If no metadata can be found for
* the given value.
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
*/
public function validate($value, $group, $propertyPath, $traverse = false, $deep = false);
@@ -75,6 +80,8 @@ public function validate($value, $group, $propertyPath, $traverse = false, $deep
* @param mixed $value The value to validate.
* @param string $group The validation group to validate.
* @param string $propertyPath The current property path in the validation graph.
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
*/
public function visit(MetadataInterface $metadata, $value, $group, $propertyPath);
}
diff --git a/src/Symfony/Component/Validator/Validator.php b/src/Symfony/Component/Validator/Validator.php
index a7bcc3a0a6cfb..72440f8f00f44 100644
--- a/src/Symfony/Component/Validator/Validator.php
+++ b/src/Symfony/Component/Validator/Validator.php
@@ -11,17 +11,20 @@
namespace Symfony\Component\Validator;
+use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Exception\ValidatorException;
-use Symfony\Component\Translation\TranslatorInterface;
/**
* Default implementation of {@link ValidatorInterface}.
*
* @author Fabien Potencier
* @author Bernhard Schussek
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Validator\RecursiveValidator} instead.
*/
-class Validator implements ValidatorInterface
+class Validator implements ValidatorInterface, Mapping\Factory\MetadataFactoryInterface
{
/**
* @var MetadataFactoryInterface
@@ -79,6 +82,14 @@ public function getMetadataFor($value)
return $this->metadataFactory->getMetadataFor($value);
}
+ /**
+ * {@inheritDoc}
+ */
+ public function hasMetadataFor($value)
+ {
+ return $this->metadataFactory->hasMetadataFor($value);
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php
new file mode 100644
index 0000000000000..83b5d0712098e
--- /dev/null
+++ b/src/Symfony/Component/Validator/Validator/ContextualValidatorInterface.php
@@ -0,0 +1,89 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Validator;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+
+/**
+ * A validator in a specific execution context.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+interface ContextualValidatorInterface
+{
+ /**
+ * Appends the given path to the property path of the context.
+ *
+ * If called multiple times, the path will always be reset to the context's
+ * original path with the given path appended to it.
+ *
+ * @param string $path The path to append
+ *
+ * @return ContextualValidatorInterface This validator
+ */
+ public function atPath($path);
+
+ /**
+ * Validates a value against a constraint or a list of constraints.
+ *
+ * If no constraint is passed, the constraint
+ * {@link \Symfony\Component\Validator\Constraints\Valid} is assumed.
+ *
+ * @param mixed $value The value to validate
+ * @param Constraint|Constraint[] $constraints The constraint(s) to validate
+ * against
+ * @param array|null $groups The validation groups to
+ * validate. If none is given,
+ * "Default" is assumed
+ *
+ * @return ContextualValidatorInterface This validator
+ */
+ public function validate($value, $constraints = null, $groups = null);
+
+ /**
+ * Validates a property of an object against the constraints specified
+ * for this property.
+ *
+ * @param object $object The object
+ * @param string $propertyName The name of the validated property
+ * @param array|null $groups The validation groups to validate. If
+ * none is given, "Default" is assumed
+ *
+ * @return ContextualValidatorInterface This validator
+ */
+ public function validateProperty($object, $propertyName, $groups = null);
+
+ /**
+ * Validates a value against the constraints specified for an object's
+ * property.
+ *
+ * @param object $object The object
+ * @param string $propertyName The name of the property
+ * @param mixed $value The value to validate against the
+ * property's constraints
+ * @param array|null $groups The validation groups to validate. If
+ * none is given, "Default" is assumed
+ *
+ * @return ContextualValidatorInterface This validator
+ */
+ public function validatePropertyValue($object, $propertyName, $value, $groups = null);
+
+ /**
+ * Returns the violations that have been generated so far in the context
+ * of the validator.
+ *
+ * @return ConstraintViolationListInterface The constraint violations
+ */
+ public function getViolations();
+}
diff --git a/src/Symfony/Component/Validator/Validator/LegacyValidator.php b/src/Symfony/Component/Validator/Validator/LegacyValidator.php
new file mode 100644
index 0000000000000..12bea285c432c
--- /dev/null
+++ b/src/Symfony/Component/Validator/Validator/LegacyValidator.php
@@ -0,0 +1,79 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Validator;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\GroupSequence;
+use Symfony\Component\Validator\Constraints\Valid;
+use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface;
+
+/**
+ * A validator that supports both the API of Symfony < 2.5 and Symfony 2.5+.
+ *
+ * This class is incompatible with PHP versions < 5.3.9, because it implements
+ * two different interfaces specifying the same method validate():
+ *
+ * - {@link \Symfony\Component\Validator\ValidatorInterface}
+ * - {@link \Symfony\Component\Validator\Validator\ValidatorInterface}
+ *
+ * In PHP versions prior to 5.3.9, either use {@link RecursiveValidator} or the
+ * deprecated class {@link \Symfony\Component\Validator\Validator} instead.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @see \Symfony\Component\Validator\ValidatorInterface
+ * @see \Symfony\Component\Validator\Validator\ValidatorInterface
+ *
+ * @deprecated Implemented for backwards compatibility with Symfony < 2.5.
+ * To be removed in Symfony 3.0.
+ */
+class LegacyValidator extends RecursiveValidator implements LegacyValidatorInterface
+{
+ public function validate($value, $groups = null, $traverse = false, $deep = false)
+ {
+ $numArgs = func_num_args();
+
+ // Use new signature if constraints are given in the second argument
+ if (self::testConstraints($groups) && ($numArgs < 2 || 3 === $numArgs && self::testGroups($traverse))) {
+ // Rename to avoid total confusion ;)
+ $constraints = $groups;
+ $groups = $traverse;
+
+ return parent::validate($value, $constraints, $groups);
+ }
+
+ $constraint = new Valid(array('traverse' => $traverse, 'deep' => $deep));
+
+ return parent::validate($value, $constraint, $groups);
+ }
+
+ public function validateValue($value, $constraints, $groups = null)
+ {
+ return parent::validate($value, $constraints, $groups);
+ }
+
+ public function getMetadataFactory()
+ {
+ return $this->metadataFactory;
+ }
+
+ private static function testConstraints($constraints)
+ {
+ return null === $constraints || $constraints instanceof Constraint || (is_array($constraints) && current($constraints) instanceof Constraint);
+ }
+
+ private static function testGroups($groups)
+ {
+ return null === $groups || is_string($groups) || $groups instanceof GroupSequence || (is_array($groups) && (is_string(current($groups)) || current($groups) instanceof GroupSequence));
+ }
+}
diff --git a/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php
new file mode 100644
index 0000000000000..0da7c7cf51104
--- /dev/null
+++ b/src/Symfony/Component/Validator/Validator/RecursiveContextualValidator.php
@@ -0,0 +1,804 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Validator;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\GroupSequence;
+use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Validator\Exception\ConstraintDefinitionException;
+use Symfony\Component\Validator\Exception\NoSuchMetadataException;
+use Symfony\Component\Validator\Exception\RuntimeException;
+use Symfony\Component\Validator\Exception\UnsupportedMetadataException;
+use Symfony\Component\Validator\Exception\ValidatorException;
+use Symfony\Component\Validator\Mapping\CascadingStrategy;
+use Symfony\Component\Validator\Mapping\ClassMetadataInterface;
+use Symfony\Component\Validator\Mapping\GenericMetadata;
+use Symfony\Component\Validator\Mapping\MetadataInterface;
+use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
+use Symfony\Component\Validator\Mapping\TraversalStrategy;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+use Symfony\Component\Validator\Util\PropertyPath;
+
+/**
+ * Recursive implementation of {@link ContextualValidatorInterface}.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+class RecursiveContextualValidator implements ContextualValidatorInterface
+{
+ /**
+ * @var ExecutionContextInterface
+ */
+ private $context;
+
+ /**
+ * @var MetadataFactoryInterface
+ */
+ private $metadataFactory;
+
+ /**
+ * @var ConstraintValidatorFactoryInterface
+ */
+ private $validatorFactory;
+
+ /**
+ * Creates a validator for the given context.
+ *
+ * @param ExecutionContextInterface $context The execution context
+ * @param MetadataFactoryInterface $metadataFactory The factory for
+ * fetching the metadata
+ * of validated objects
+ * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
+ * constraint validators
+ */
+ public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory)
+ {
+ $this->context = $context;
+ $this->defaultPropertyPath = $context->getPropertyPath();
+ $this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP);
+ $this->metadataFactory = $metadataFactory;
+ $this->validatorFactory = $validatorFactory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function atPath($path)
+ {
+ $this->defaultPropertyPath = $this->context->getPropertyPath($path);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validate($value, $constraints = null, $groups = null)
+ {
+ $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
+
+ // If explicit constraints are passed, validate the value against
+ // those constraints
+ if (null !== $constraints) {
+ // You can pass a single constraint or an array of constraints
+ // Make sure to deal with an array in the rest of the code
+ if (!is_array($constraints)) {
+ $constraints = array($constraints);
+ }
+
+ $metadata = new GenericMetadata();
+ $metadata->addConstraints($constraints);
+
+ $this->validateGenericNode(
+ $value,
+ null,
+ is_object($value) ? spl_object_hash($value) : null,
+ $metadata,
+ $this->defaultPropertyPath,
+ $groups,
+ null,
+ TraversalStrategy::IMPLICIT,
+ $this->context
+ );
+
+ return $this;
+ }
+
+ // If an object is passed without explicit constraints, validate that
+ // object against the constraints defined for the object's class
+ if (is_object($value)) {
+ $this->validateObject(
+ $value,
+ $this->defaultPropertyPath,
+ $groups,
+ TraversalStrategy::IMPLICIT,
+ $this->context
+ );
+
+ return $this;
+ }
+
+ // If an array is passed without explicit constraints, validate each
+ // object in the array
+ if (is_array($value)) {
+ $this->validateEachObjectIn(
+ $value,
+ $this->defaultPropertyPath,
+ $groups,
+ true,
+ $this->context
+ );
+
+ return $this;
+ }
+
+ throw new RuntimeException(sprintf(
+ 'Cannot validate values of type "%s" automatically. Please '.
+ 'provide a constraint.',
+ gettype($value)
+ ));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateProperty($object, $propertyName, $groups = null)
+ {
+ $classMetadata = $this->metadataFactory->getMetadataFor($object);
+
+ if (!$classMetadata instanceof ClassMetadataInterface) {
+ // Cannot be UnsupportedMetadataException because of BC with
+ // Symfony < 2.5
+ throw new ValidatorException(sprintf(
+ 'The metadata factory should return instances of '.
+ '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
+ 'got: "%s".',
+ is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
+ ));
+ }
+
+ $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
+ $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
+ $cacheKey = spl_object_hash($object);
+
+ foreach ($propertyMetadatas as $propertyMetadata) {
+ $propertyValue = $propertyMetadata->getPropertyValue($object);
+
+ $this->validateGenericNode(
+ $propertyValue,
+ $object,
+ $cacheKey.':'.$propertyName,
+ $propertyMetadata,
+ PropertyPath::append($this->defaultPropertyPath, $propertyName),
+ $groups,
+ null,
+ TraversalStrategy::IMPLICIT,
+ $this->context
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validatePropertyValue($object, $propertyName, $value, $groups = null)
+ {
+ $classMetadata = $this->metadataFactory->getMetadataFor($object);
+
+ if (!$classMetadata instanceof ClassMetadataInterface) {
+ // Cannot be UnsupportedMetadataException because of BC with
+ // Symfony < 2.5
+ throw new ValidatorException(sprintf(
+ 'The metadata factory should return instances of '.
+ '"\Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
+ 'got: "%s".',
+ is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
+ ));
+ }
+
+ $propertyMetadatas = $classMetadata->getPropertyMetadata($propertyName);
+ $groups = $groups ? $this->normalizeGroups($groups) : $this->defaultGroups;
+ $cacheKey = spl_object_hash($object);
+
+ foreach ($propertyMetadatas as $propertyMetadata) {
+ $this->validateGenericNode(
+ $value,
+ $object,
+ $cacheKey.':'.$propertyName,
+ $propertyMetadata,
+ PropertyPath::append($this->defaultPropertyPath, $propertyName),
+ $groups,
+ null,
+ TraversalStrategy::IMPLICIT,
+ $this->context
+ );
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getViolations()
+ {
+ return $this->context->getViolations();
+ }
+
+ /**
+ * Normalizes the given group or list of groups to an array.
+ *
+ * @param mixed $groups The groups to normalize
+ *
+ * @return array A group array
+ */
+ protected function normalizeGroups($groups)
+ {
+ if (is_array($groups)) {
+ return $groups;
+ }
+
+ return array($groups);
+ }
+ /**
+ * Validates an object against the constraints defined for its class.
+ *
+ * If no metadata is available for the class, but the class is an instance
+ * of {@link \Traversable} and the selected traversal strategy allows
+ * traversal, the object will be iterated and each nested object will be
+ * validated instead.
+ *
+ * @param object $object The object to cascade
+ * @param string $propertyPath The current property path
+ * @param string[] $groups The validated groups
+ * @param integer $traversalStrategy The strategy for traversing the
+ * cascaded object
+ * @param ExecutionContextInterface $context The current execution context
+ *
+ * @throws NoSuchMetadataException If the object has no associated metadata
+ * and does not implement {@link \Traversable}
+ * or if traversal is disabled via the
+ * $traversalStrategy argument
+ * @throws UnsupportedMetadataException If the metadata returned by the
+ * metadata factory does not implement
+ * {@link ClassMetadataInterface}
+ */
+ private function validateObject($object, $propertyPath, array $groups, $traversalStrategy, ExecutionContextInterface $context)
+ {
+ try {
+ $classMetadata = $this->metadataFactory->getMetadataFor($object);
+
+ if (!$classMetadata instanceof ClassMetadataInterface) {
+ throw new UnsupportedMetadataException(sprintf(
+ 'The metadata factory should return instances of '.
+ '"Symfony\Component\Validator\Mapping\ClassMetadataInterface", '.
+ 'got: "%s".',
+ is_object($classMetadata) ? get_class($classMetadata) : gettype($classMetadata)
+ ));
+ }
+
+ $this->validateClassNode(
+ $object,
+ spl_object_hash($object),
+ $classMetadata,
+ $propertyPath,
+ $groups,
+ null,
+ $traversalStrategy,
+ $context
+ );
+ } catch (NoSuchMetadataException $e) {
+ // Rethrow if not Traversable
+ if (!$object instanceof \Traversable) {
+ throw $e;
+ }
+
+ // Rethrow unless IMPLICIT or TRAVERSE
+ if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
+ throw $e;
+ }
+
+ $this->validateEachObjectIn(
+ $object,
+ $propertyPath,
+ $groups,
+ $traversalStrategy & TraversalStrategy::STOP_RECURSION,
+ $context
+ );
+ }
+ }
+
+ /**
+ * Validates each object in a collection against the constraints defined
+ * for their classes.
+ *
+ * If the parameter $recursive is set to true, nested {@link \Traversable}
+ * objects are iterated as well. Nested arrays are always iterated,
+ * regardless of the value of $recursive.
+ *
+ * @param array|\Traversable $collection The collection
+ * @param string $propertyPath The current property path
+ * @param string[] $groups The validated groups
+ * @param Boolean $stopRecursion Whether to disable
+ * recursive iteration. For
+ * backwards compatibility
+ * with Symfony < 2.5.
+ * @param ExecutionContextInterface $context The current execution context
+ *
+ * @see ClassNode
+ * @see CollectionNode
+ */
+ private function validateEachObjectIn($collection, $propertyPath, array $groups, $stopRecursion, ExecutionContextInterface $context)
+ {
+ if ($stopRecursion) {
+ $traversalStrategy = TraversalStrategy::NONE;
+ } else {
+ $traversalStrategy = TraversalStrategy::IMPLICIT;
+ }
+
+ foreach ($collection as $key => $value) {
+ if (is_array($value)) {
+ // Arrays are always cascaded, independent of the specified
+ // traversal strategy
+ // (BC with Symfony < 2.5)
+ $this->validateEachObjectIn(
+ $value,
+ $propertyPath.'['.$key.']',
+ $groups,
+ $stopRecursion,
+ $context
+ );
+
+ continue;
+ }
+
+ // Scalar and null values in the collection are ignored
+ // (BC with Symfony < 2.5)
+ if (is_object($value)) {
+ $this->validateObject(
+ $value,
+ $propertyPath.'['.$key.']',
+ $groups,
+ $traversalStrategy,
+ $context
+ );
+ }
+ }
+ }
+
+ /**
+ * Validates a class node.
+ *
+ * A class node is a combination of an object with a {@link ClassMetadataInterface}
+ * instance. Each class node (conceptionally) has zero or more succeeding
+ * property nodes:
+ *
+ * (Article:class node)
+ * \
+ * ($title:property node)
+ *
+ * This method validates the passed objects against all constraints defined
+ * at class level. It furthermore triggers the validation of each of the
+ * class' properties against the constraints for that property.
+ *
+ * If the selected traversal strategy allows traversal, the object is
+ * iterated and each nested object is validated against its own constraints.
+ * The object is not traversed if traversal is disabled in the class
+ * metadata.
+ *
+ * If the passed groups contain the group "Default", the validator will
+ * check whether the "Default" group has been replaced by a group sequence
+ * in the class metadata. If this is the case, the group sequence is
+ * validated instead.
+ *
+ * @param object $object The validated object
+ * @param string $cacheKey The key for caching
+ * the validated object
+ * @param ClassMetadataInterface $metadata The class metadata of
+ * the object
+ * @param string $propertyPath The property path leading
+ * to the object
+ * @param string[] $groups The groups in which the
+ * object should be validated
+ * @param string[]|null $cascadedGroups The groups in which
+ * cascaded objects should
+ * be validated
+ * @param integer $traversalStrategy The strategy used for
+ * traversing the object
+ * @param ExecutionContextInterface $context The current execution context
+ *
+ * @throws UnsupportedMetadataException If a property metadata does not
+ * implement {@link PropertyMetadataInterface}
+ * @throws ConstraintDefinitionException If traversal was enabled but the
+ * object does not implement
+ * {@link \Traversable}
+ *
+ * @see TraversalStrategy
+ */
+ private function validateClassNode($object, $cacheKey, ClassMetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
+ {
+ $context->setNode($object, $object, $metadata, $propertyPath);
+
+ foreach ($groups as $key => $group) {
+ // If the "Default" group is replaced by a group sequence, remember
+ // to cascade the "Default" group when traversing the group
+ // sequence
+ $defaultOverridden = false;
+
+ // Use the object hash for group sequences
+ $groupHash = is_object($group) ? spl_object_hash($group) : $group;
+
+ if ($context->isGroupValidated($cacheKey, $groupHash)) {
+ // Skip this group when validating the properties and when
+ // traversing the object
+ unset($groups[$key]);
+
+ continue;
+ }
+
+ $context->markGroupAsValidated($cacheKey, $groupHash);
+
+ // Replace the "Default" group by the group sequence defined
+ // for the class, if applicable.
+ // This is done after checking the cache, so that
+ // spl_object_hash() isn't called for this sequence and
+ // "Default" is used instead in the cache. This is useful
+ // if the getters below return different group sequences in
+ // every call.
+ if (Constraint::DEFAULT_GROUP === $group) {
+ if ($metadata->hasGroupSequence()) {
+ // The group sequence is statically defined for the class
+ $group = $metadata->getGroupSequence();
+ $defaultOverridden = true;
+ } elseif ($metadata->isGroupSequenceProvider()) {
+ // The group sequence is dynamically obtained from the validated
+ // object
+ /** @var \Symfony\Component\Validator\GroupSequenceProviderInterface $object */
+ $group = $object->getGroupSequence();
+ $defaultOverridden = true;
+
+ if (!$group instanceof GroupSequence) {
+ $group = new GroupSequence($group);
+ }
+ }
+ }
+
+ // If the groups (=[,G3,G4]) contain a group sequence
+ // (=), then call validateClassNode() with each entry of the
+ // group sequence and abort if necessary (G1, G2)
+ if ($group instanceof GroupSequence) {
+ $this->stepThroughGroupSequence(
+ $object,
+ $object,
+ $cacheKey,
+ $metadata,
+ $propertyPath,
+ $traversalStrategy,
+ $group,
+ $defaultOverridden ? Constraint::DEFAULT_GROUP : null,
+ $context
+ );
+
+ // Skip the group sequence when validating properties, because
+ // stepThroughGroupSequence() already validates the properties
+ unset($groups[$key]);
+
+ continue;
+ }
+
+ $this->validateInGroup($object, $cacheKey, $metadata, $group, $context);
+ }
+
+ // If no more groups should be validated for the property nodes,
+ // we can safely quit
+ if (0 === count($groups)) {
+ return;
+ }
+
+ // Validate all properties against their constraints
+ foreach ($metadata->getConstrainedProperties() as $propertyName) {
+ // If constraints are defined both on the getter of a property as
+ // well as on the property itself, then getPropertyMetadata()
+ // returns two metadata objects, not just one
+ foreach ($metadata->getPropertyMetadata($propertyName) as $propertyMetadata) {
+ if (!$propertyMetadata instanceof PropertyMetadataInterface) {
+ throw new UnsupportedMetadataException(sprintf(
+ 'The property metadata instances should implement '.
+ '"Symfony\Component\Validator\Mapping\PropertyMetadataInterface", '.
+ 'got: "%s".',
+ is_object($propertyMetadata) ? get_class($propertyMetadata) : gettype($propertyMetadata)
+ ));
+ }
+
+ $propertyValue = $propertyMetadata->getPropertyValue($object);
+
+ $this->validateGenericNode(
+ $propertyValue,
+ $object,
+ $cacheKey.':'.$propertyName,
+ $propertyMetadata,
+ $propertyPath
+ ? $propertyPath.'.'.$propertyName
+ : $propertyName,
+ $groups,
+ $cascadedGroups,
+ TraversalStrategy::IMPLICIT,
+ $context
+ );
+ }
+ }
+
+ // If no specific traversal strategy was requested when this method
+ // was called, use the traversal strategy of the class' metadata
+ if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
+ // Keep the STOP_RECURSION flag, if it was set
+ $traversalStrategy = $metadata->getTraversalStrategy()
+ | ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
+ }
+
+ // Traverse only if IMPLICIT or TRAVERSE
+ if (!($traversalStrategy & (TraversalStrategy::IMPLICIT | TraversalStrategy::TRAVERSE))) {
+ return;
+ }
+
+ // If IMPLICIT, stop unless we deal with a Traversable
+ if ($traversalStrategy & TraversalStrategy::IMPLICIT && !$object instanceof \Traversable) {
+ return;
+ }
+
+ // If TRAVERSE, fail if we have no Traversable
+ if (!$object instanceof \Traversable) {
+ // Must throw a ConstraintDefinitionException for backwards
+ // compatibility reasons with Symfony < 2.5
+ throw new ConstraintDefinitionException(sprintf(
+ 'Traversal was enabled for "%s", but this class '.
+ 'does not implement "\Traversable".',
+ get_class($object)
+ ));
+ }
+
+ $this->validateEachObjectIn(
+ $object,
+ $propertyPath,
+ $groups,
+ $traversalStrategy & TraversalStrategy::STOP_RECURSION,
+ $context
+ );
+ }
+
+ /**
+ * Validates a node that is not a class node.
+ *
+ * Currently, two such node types exist:
+ *
+ * - property nodes, which consist of the value of an object's
+ * property together with a {@link PropertyMetadataInterface} instance
+ * - generic nodes, which consist of a value and some arbitrary
+ * constraints defined in a {@link MetadataInterface} container
+ *
+ * In both cases, the value is validated against all constraints defined
+ * in the passed metadata object. Then, if the value is an instance of
+ * {@link \Traversable} and the selected traversal strategy permits it,
+ * the value is traversed and each nested object validated against its own
+ * constraints. Arrays are always traversed.
+ *
+ * @param mixed $value The validated value
+ * @param object|null $object The current object
+ * @param string $cacheKey The key for caching
+ * the validated value
+ * @param MetadataInterface $metadata The metadata of the
+ * value
+ * @param string $propertyPath The property path leading
+ * to the value
+ * @param string[] $groups The groups in which the
+ * value should be validated
+ * @param string[]|null $cascadedGroups The groups in which
+ * cascaded objects should
+ * be validated
+ * @param integer $traversalStrategy The strategy used for
+ * traversing the value
+ * @param ExecutionContextInterface $context The current execution context
+ *
+ * @see TraversalStrategy
+ */
+ private function validateGenericNode($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, array $groups, $cascadedGroups, $traversalStrategy, ExecutionContextInterface $context)
+ {
+ $context->setNode($value, $object, $metadata, $propertyPath);
+
+ foreach ($groups as $key => $group) {
+ if ($group instanceof GroupSequence) {
+ $this->stepThroughGroupSequence(
+ $value,
+ $object,
+ $cacheKey,
+ $metadata,
+ $propertyPath,
+ $traversalStrategy,
+ $group,
+ null,
+ $context
+ );
+
+ // Skip the group sequence when cascading, as the cascading
+ // logic is already done in stepThroughGroupSequence()
+ unset($groups[$key]);
+
+ continue;
+ }
+
+ $this->validateInGroup($value, $cacheKey, $metadata, $group, $context);
+ }
+
+ if (0 === count($groups)) {
+ return;
+ }
+
+ if (null === $value) {
+ return;
+ }
+
+ $cascadingStrategy = $metadata->getCascadingStrategy();
+
+ // Quit unless we have an array or a cascaded object
+ if (!is_array($value) && !($cascadingStrategy & CascadingStrategy::CASCADE)) {
+ return;
+ }
+
+ // If no specific traversal strategy was requested when this method
+ // was called, use the traversal strategy of the node's metadata
+ if ($traversalStrategy & TraversalStrategy::IMPLICIT) {
+ // Keep the STOP_RECURSION flag, if it was set
+ $traversalStrategy = $metadata->getTraversalStrategy()
+ | ($traversalStrategy & TraversalStrategy::STOP_RECURSION);
+ }
+
+ // The $cascadedGroups property is set, if the "Default" group is
+ // overridden by a group sequence
+ // See validateClassNode()
+ $cascadedGroups = count($cascadedGroups) > 0
+ ? $cascadedGroups
+ : $groups;
+
+ if (is_array($value)) {
+ // Arrays are always traversed, independent of the specified
+ // traversal strategy
+ // (BC with Symfony < 2.5)
+ $this->validateEachObjectIn(
+ $value,
+ $propertyPath,
+ $cascadedGroups,
+ $traversalStrategy & TraversalStrategy::STOP_RECURSION,
+ $context
+ );
+
+ return;
+ }
+
+ // If the value is a scalar, pass it anyway, because we want
+ // a NoSuchMetadataException to be thrown in that case
+ // (BC with Symfony < 2.5)
+ $this->validateObject(
+ $value,
+ $propertyPath,
+ $cascadedGroups,
+ $traversalStrategy,
+ $context
+ );
+
+ // Currently, the traversal strategy can only be TRAVERSE for a
+ // generic node if the cascading strategy is CASCADE. Thus, traversable
+ // objects will always be handled within validateObject() and there's
+ // nothing more to do here.
+
+ // see GenericMetadata::addConstraint()
+ }
+
+ /**
+ * Sequentially validates a node's value in each group of a group sequence.
+ *
+ * If any of the constraints generates a violation, subsequent groups in the
+ * group sequence are skipped.
+ *
+ * @param mixed $value The validated value
+ * @param object|null $object The current object
+ * @param string $cacheKey The key for caching
+ * the validated value
+ * @param MetadataInterface $metadata The metadata of the
+ * value
+ * @param string $propertyPath The property path leading
+ * to the value
+ * @param integer $traversalStrategy The strategy used for
+ * traversing the value
+ * @param GroupSequence $groupSequence The group sequence
+ * @param string[]|null $cascadedGroup The group that should
+ * be passed to cascaded
+ * objects instead of
+ * the group sequence
+ * @param ExecutionContextInterface $context The execution context
+ */
+ private function stepThroughGroupSequence($value, $object, $cacheKey, MetadataInterface $metadata = null, $propertyPath, $traversalStrategy, GroupSequence $groupSequence, $cascadedGroup, ExecutionContextInterface $context)
+ {
+ $violationCount = count($context->getViolations());
+ $cascadedGroups = $cascadedGroup ? array($cascadedGroup) : null;
+
+ foreach ($groupSequence->groups as $groupInSequence) {
+ $groups = array($groupInSequence);
+
+ if ($metadata instanceof ClassMetadataInterface) {
+ $this->validateClassNode(
+ $value,
+ $cacheKey,
+ $metadata,
+ $propertyPath,
+ $groups,
+ $cascadedGroups,
+ $traversalStrategy,
+ $context
+ );
+ } else {
+ $this->validateGenericNode(
+ $value,
+ $object,
+ $cacheKey,
+ $metadata,
+ $propertyPath,
+ $groups,
+ $cascadedGroups,
+ $traversalStrategy,
+ $context
+ );
+ }
+
+ // Abort sequence validation if a violation was generated
+ if (count($context->getViolations()) > $violationCount) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Validates a node's value against all constraints in the given group.
+ *
+ * @param mixed $value The validated value
+ * @param string $cacheKey The key for caching the
+ * validated value
+ * @param MetadataInterface $metadata The metadata of the value
+ * @param string $group The group to validate
+ * @param ExecutionContextInterface $context The execution context
+ */
+ private function validateInGroup($value, $cacheKey, MetadataInterface $metadata, $group, ExecutionContextInterface $context)
+ {
+ $context->setGroup($group);
+
+ foreach ($metadata->findConstraints($group) as $constraint) {
+ // Prevent duplicate validation of constraints, in the case
+ // that constraints belong to multiple validated groups
+ if (null !== $cacheKey) {
+ $constraintHash = spl_object_hash($constraint);
+
+ if ($context->isConstraintValidated($cacheKey, $constraintHash)) {
+ continue;
+ }
+
+ $context->markConstraintAsValidated($cacheKey, $constraintHash);
+ }
+
+ $validator = $this->validatorFactory->getInstance($constraint);
+ $validator->initialize($context);
+ $validator->validate($value, $constraint);
+ }
+ }
+}
diff --git a/src/Symfony/Component/Validator/Validator/RecursiveValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveValidator.php
new file mode 100644
index 0000000000000..d0a66f3d8a740
--- /dev/null
+++ b/src/Symfony/Component/Validator/Validator/RecursiveValidator.php
@@ -0,0 +1,129 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Validator;
+
+use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
+use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Validator\MetadataFactoryInterface;
+
+/**
+ * Recursive implementation of {@link ValidatorInterface}.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+class RecursiveValidator implements ValidatorInterface
+{
+ /**
+ * @var ExecutionContextFactoryInterface
+ */
+ protected $contextFactory;
+
+ /**
+ * @var MetadataFactoryInterface
+ */
+ protected $metadataFactory;
+
+ /**
+ * @var ConstraintValidatorFactoryInterface
+ */
+ protected $validatorFactory;
+
+ /**
+ * Creates a new validator.
+ *
+ * @param ExecutionContextFactoryInterface $contextFactory The factory for
+ * creating new contexts
+ * @param MetadataFactoryInterface $metadataFactory The factory for
+ * fetching the metadata
+ * of validated objects
+ * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
+ * constraint validators
+ */
+ public function __construct(ExecutionContextFactoryInterface $contextFactory, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory)
+ {
+ $this->contextFactory = $contextFactory;
+ $this->metadataFactory = $metadataFactory;
+ $this->validatorFactory = $validatorFactory;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function startContext($root = null)
+ {
+ return new RecursiveContextualValidator(
+ $this->contextFactory->createContext($this, $root),
+ $this->metadataFactory,
+ $this->validatorFactory
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function inContext(ExecutionContextInterface $context)
+ {
+ return new RecursiveContextualValidator(
+ $context,
+ $this->metadataFactory,
+ $this->validatorFactory
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadataFor($object)
+ {
+ return $this->metadataFactory->getMetadataFor($object);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasMetadataFor($object)
+ {
+ return $this->metadataFactory->hasMetadataFor($object);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validate($value, $constraints = null, $groups = null)
+ {
+ return $this->startContext($value)
+ ->validate($value, $constraints, $groups)
+ ->getViolations();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateProperty($object, $propertyName, $groups = null)
+ {
+ return $this->startContext($object)
+ ->validateProperty($object, $propertyName, $groups)
+ ->getViolations();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validatePropertyValue($object, $propertyName, $value, $groups = null)
+ {
+ return $this->startContext($object)
+ ->validatePropertyValue($object, $propertyName, $value, $groups)
+ ->getViolations();
+ }
+}
diff --git a/src/Symfony/Component/Validator/Validator/ValidatorInterface.php b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php
new file mode 100644
index 0000000000000..acbb28f03a2d9
--- /dev/null
+++ b/src/Symfony/Component/Validator/Validator/ValidatorInterface.php
@@ -0,0 +1,100 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Validator;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintViolationListInterface;
+use Symfony\Component\Validator\Context\ExecutionContextInterface;
+use Symfony\Component\Validator\Mapping\Factory\MetadataFactoryInterface;
+
+/**
+ * Validates PHP values against constraints.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+interface ValidatorInterface extends MetadataFactoryInterface
+{
+ /**
+ * Validates a value against a constraint or a list of constraints.
+ *
+ * If no constraint is passed, the constraint
+ * {@link \Symfony\Component\Validator\Constraints\Valid} is assumed.
+ *
+ * @param mixed $value The value to validate
+ * @param Constraint|Constraint[] $constraints The constraint(s) to validate
+ * against
+ * @param array|null $groups The validation groups to
+ * validate. If none is given,
+ * "Default" is assumed
+ *
+ * @return ConstraintViolationListInterface A list of constraint violations.
+ * If the list is empty, validation
+ * succeeded
+ */
+ public function validate($value, $constraints = null, $groups = null);
+
+ /**
+ * Validates a property of an object against the constraints specified
+ * for this property.
+ *
+ * @param object $object The object
+ * @param string $propertyName The name of the validated property
+ * @param array|null $groups The validation groups to validate. If
+ * none is given, "Default" is assumed
+ *
+ * @return ConstraintViolationListInterface A list of constraint violations.
+ * If the list is empty, validation
+ * succeeded
+ */
+ public function validateProperty($object, $propertyName, $groups = null);
+
+ /**
+ * Validates a value against the constraints specified for an object's
+ * property.
+ *
+ * @param object $object The object
+ * @param string $propertyName The name of the property
+ * @param mixed $value The value to validate against the
+ * property's constraints
+ * @param array|null $groups The validation groups to validate. If
+ * none is given, "Default" is assumed
+ *
+ * @return ConstraintViolationListInterface A list of constraint violations.
+ * If the list is empty, validation
+ * succeeded
+ */
+ public function validatePropertyValue($object, $propertyName, $value, $groups = null);
+
+ /**
+ * Starts a new validation context and returns a validator for that context.
+ *
+ * The returned validator collects all violations generated within its
+ * context. You can access these violations with the
+ * {@link ContextualValidatorInterface::getViolations()} method.
+ *
+ * @return ContextualValidatorInterface The validator for the new context
+ */
+ public function startContext();
+
+ /**
+ * Returns a validator in the given execution context.
+ *
+ * The returned validator adds all generated violations to the given
+ * context.
+ *
+ * @param ExecutionContextInterface $context The execution context
+ *
+ * @return ContextualValidatorInterface The validator for that context
+ */
+ public function inContext(ExecutionContextInterface $context);
+}
diff --git a/src/Symfony/Component/Validator/ValidatorBuilder.php b/src/Symfony/Component/Validator/ValidatorBuilder.php
index e24a7071662e2..6f33a8d222a00 100644
--- a/src/Symfony/Component/Validator/ValidatorBuilder.php
+++ b/src/Symfony/Component/Validator/ValidatorBuilder.php
@@ -11,23 +11,28 @@
namespace Symfony\Component\Validator;
-use Symfony\Component\PropertyAccess\PropertyAccess;
+use Doctrine\Common\Annotations\AnnotationReader;
+use Doctrine\Common\Annotations\AnnotationRegistry;
+use Doctrine\Common\Annotations\CachedReader;
+use Doctrine\Common\Annotations\Reader;
+use Doctrine\Common\Cache\ArrayCache;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
-use Symfony\Component\Validator\Mapping\ClassMetadataFactory;
+use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Component\Validator\Context\LegacyExecutionContextFactory;
+use Symfony\Component\Validator\Exception\InvalidArgumentException;
use Symfony\Component\Validator\Exception\ValidatorException;
-use Symfony\Component\Validator\Mapping\Loader\LoaderChain;
use Symfony\Component\Validator\Mapping\Cache\CacheInterface;
-use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader;
-use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;
+use Symfony\Component\Validator\Mapping\ClassMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader;
-use Symfony\Component\Validator\Mapping\Loader\YamlFilesLoader;
+use Symfony\Component\Validator\Mapping\Loader\LoaderChain;
+use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader;
use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Validator\Mapping\Loader\XmlFilesLoader;
-use Symfony\Component\Translation\TranslatorInterface;
-use Doctrine\Common\Annotations\Reader;
-use Doctrine\Common\Annotations\AnnotationReader;
-use Doctrine\Common\Annotations\CachedReader;
-use Doctrine\Common\Cache\ArrayCache;
+use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;
+use Symfony\Component\Validator\Mapping\Loader\YamlFilesLoader;
+use Symfony\Component\Validator\Validator\LegacyValidator;
+use Symfony\Component\Validator\Validator\RecursiveValidator;
+use Symfony\Component\Validator\Validator as ValidatorV24;
/**
* The default implementation of {@link ValidatorBuilderInterface}.
@@ -91,6 +96,11 @@ class ValidatorBuilder implements ValidatorBuilderInterface
*/
private $propertyAccessor;
+ /**
+ * @var integer
+ */
+ private $apiVersion;
+
/**
* {@inheritdoc}
*/
@@ -303,6 +313,32 @@ public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
return $this;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function setApiVersion($apiVersion)
+ {
+ if (!in_array($apiVersion, array(Validation::API_VERSION_2_4, Validation::API_VERSION_2_5, Validation::API_VERSION_2_5_BC))) {
+ throw new InvalidArgumentException(sprintf(
+ 'The requested API version is invalid: "%s"',
+ $apiVersion
+ ));
+ }
+
+ if (version_compare(PHP_VERSION, '5.3.9', '<') && $apiVersion === Validation::API_VERSION_2_5_BC) {
+ throw new InvalidArgumentException(sprintf(
+ 'The Validator API that is compatible with both Symfony 2.4 '.
+ 'and Symfony 2.5 can only be used on PHP 5.3.9 and higher. '.
+ 'Your current PHP version is %s.',
+ PHP_VERSION
+ ));
+ }
+
+ $this->apiVersion = $apiVersion;
+
+ return $this;
+ }
+
/**
* {@inheritdoc}
*/
@@ -331,6 +367,20 @@ public function getValidator()
if ($this->annotationReader) {
$loaders[] = new AnnotationLoader($this->annotationReader);
+
+ AnnotationRegistry::registerLoader(function ($class) {
+ if (0 === strpos($class, __NAMESPACE__.'\\Constraints\\')) {
+ $file = str_replace(__NAMESPACE__.'\\Constraints\\', __DIR__.'/Constraints/', $class).'.php';
+
+ if (is_file($file)) {
+ require_once $file;
+
+ return true;
+ }
+ }
+
+ return false;
+ });
}
$loader = null;
@@ -344,10 +394,26 @@ public function getValidator()
$metadataFactory = new ClassMetadataFactory($loader, $this->metadataCache);
}
- $propertyAccessor = $this->propertyAccessor ?: PropertyAccess::createPropertyAccessor();
- $validatorFactory = $this->validatorFactory ?: new ConstraintValidatorFactory($propertyAccessor);
+ $validatorFactory = $this->validatorFactory ?: new ConstraintValidatorFactory($this->propertyAccessor);
$translator = $this->translator ?: new DefaultTranslator();
+ $apiVersion = $this->apiVersion;
+
+ if (null === $apiVersion) {
+ $apiVersion = version_compare(PHP_VERSION, '5.3.9', '<')
+ ? Validation::API_VERSION_2_4
+ : Validation::API_VERSION_2_5_BC;
+ }
+
+ if (Validation::API_VERSION_2_4 === $apiVersion) {
+ return new ValidatorV24($metadataFactory, $validatorFactory, $translator, $this->translationDomain, $this->initializers);
+ }
+
+ $contextFactory = new LegacyExecutionContextFactory($metadataFactory, $translator, $this->translationDomain);
+
+ if (Validation::API_VERSION_2_5 === $apiVersion) {
+ return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory);
+ }
- return new Validator($metadataFactory, $validatorFactory, $translator, $this->translationDomain, $this->initializers);
+ return new LegacyValidator($contextFactory, $metadataFactory, $validatorFactory);
}
}
diff --git a/src/Symfony/Component/Validator/ValidatorBuilderInterface.php b/src/Symfony/Component/Validator/ValidatorBuilderInterface.php
index 92aaca756a3bc..38cf9772d0af2 100644
--- a/src/Symfony/Component/Validator/ValidatorBuilderInterface.php
+++ b/src/Symfony/Component/Validator/ValidatorBuilderInterface.php
@@ -11,10 +11,10 @@
namespace Symfony\Component\Validator;
+use Doctrine\Common\Annotations\Reader;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
-use Symfony\Component\Validator\Mapping\Cache\CacheInterface;
use Symfony\Component\Translation\TranslatorInterface;
-use Doctrine\Common\Annotations\Reader;
+use Symfony\Component\Validator\Mapping\Cache\CacheInterface;
/**
* A configurable builder for ValidatorInterface objects.
@@ -26,124 +26,124 @@ interface ValidatorBuilderInterface
/**
* Adds an object initializer to the validator.
*
- * @param ObjectInitializerInterface $initializer The initializer.
+ * @param ObjectInitializerInterface $initializer The initializer
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function addObjectInitializer(ObjectInitializerInterface $initializer);
/**
* Adds a list of object initializers to the validator.
*
- * @param array $initializers The initializer.
+ * @param array $initializers The initializer
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function addObjectInitializers(array $initializers);
/**
* Adds an XML constraint mapping file to the validator.
*
- * @param string $path The path to the mapping file.
+ * @param string $path The path to the mapping file
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function addXmlMapping($path);
/**
* Adds a list of XML constraint mapping files to the validator.
*
- * @param array $paths The paths to the mapping files.
+ * @param array $paths The paths to the mapping files
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function addXmlMappings(array $paths);
/**
* Adds a YAML constraint mapping file to the validator.
*
- * @param string $path The path to the mapping file.
+ * @param string $path The path to the mapping file
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function addYamlMapping($path);
/**
* Adds a list of YAML constraint mappings file to the validator.
*
- * @param array $paths The paths to the mapping files.
+ * @param array $paths The paths to the mapping files
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function addYamlMappings(array $paths);
/**
* Enables constraint mapping using the given static method.
*
- * @param string $methodName The name of the method.
+ * @param string $methodName The name of the method
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function addMethodMapping($methodName);
/**
* Enables constraint mapping using the given static methods.
*
- * @param array $methodNames The names of the methods.
+ * @param array $methodNames The names of the methods
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function addMethodMappings(array $methodNames);
/**
* Enables annotation based constraint mapping.
*
- * @param Reader $annotationReader The annotation reader to be used.
+ * @param Reader $annotationReader The annotation reader to be used
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function enableAnnotationMapping(Reader $annotationReader = null);
/**
* Disables annotation based constraint mapping.
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function disableAnnotationMapping();
/**
* Sets the class metadata factory used by the validator.
*
- * @param MetadataFactoryInterface $metadataFactory The metadata factory.
+ * @param MetadataFactoryInterface $metadataFactory The metadata factory
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function setMetadataFactory(MetadataFactoryInterface $metadataFactory);
/**
* Sets the cache for caching class metadata.
*
- * @param CacheInterface $cache The cache instance.
+ * @param CacheInterface $cache The cache instance
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function setMetadataCache(CacheInterface $cache);
/**
* Sets the constraint validator factory used by the validator.
*
- * @param ConstraintValidatorFactoryInterface $validatorFactory The validator factory.
+ * @param ConstraintValidatorFactoryInterface $validatorFactory The validator factory
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function setConstraintValidatorFactory(ConstraintValidatorFactoryInterface $validatorFactory);
/**
* Sets the translator used for translating violation messages.
*
- * @param TranslatorInterface $translator The translator instance.
+ * @param TranslatorInterface $translator The translator instance
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function setTranslator(TranslatorInterface $translator);
@@ -154,21 +154,36 @@ public function setTranslator(TranslatorInterface $translator);
* Pass the domain that is used for violation messages by default to this
* method.
*
- * @param string $translationDomain The translation domain of the violation messages.
+ * @param string $translationDomain The translation domain of the violation messages
*
- * @return ValidatorBuilderInterface The builder object.
+ * @return ValidatorBuilderInterface The builder object
*/
public function setTranslationDomain($translationDomain);
/**
* Sets the property accessor for resolving property paths.
*
- * @param PropertyAccessorInterface $propertyAccessor The property accessor.
+ * @param PropertyAccessorInterface $propertyAccessor The property accessor
+ *
+ * @return ValidatorBuilderInterface The builder object
*
- * @return ValidatorBuilderInterface The builder object.
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor);
+ /**
+ * Sets the API version that the returned validator should support.
+ *
+ * @param integer $apiVersion The required API version
+ *
+ * @return ValidatorBuilderInterface The builder object
+ *
+ * @see Validation::API_VERSION_2_4
+ * @see Validation::API_VERSION_2_5
+ * @see Validation::API_VERSION_2_5_BC
+ */
+ public function setApiVersion($apiVersion);
+
/**
* Builds and returns a new validator object.
*
diff --git a/src/Symfony/Component/Validator/ValidatorInterface.php b/src/Symfony/Component/Validator/ValidatorInterface.php
index 98e02d90cf4d8..e56be78221b98 100644
--- a/src/Symfony/Component/Validator/ValidatorInterface.php
+++ b/src/Symfony/Component/Validator/ValidatorInterface.php
@@ -17,6 +17,9 @@
* @author Bernhard Schussek
*
* @api
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Validator\ValidatorInterface} instead.
*/
interface ValidatorInterface
{
@@ -26,6 +29,10 @@ interface ValidatorInterface
* The accepted values depend on the {@link MetadataFactoryInterface}
* implementation.
*
+ * The signature changed with Symfony 2.5 (see
+ * {@link Validator\ValidatorInterface::validate()}. This signature will be
+ * disabled in Symfony 3.0.
+ *
* @param mixed $value The value to validate
* @param array|null $groups The validation groups to validate.
* @param Boolean $traverse Whether to traverse the value if it is traversable.
@@ -85,6 +92,9 @@ public function validatePropertyValue($containingValue, $property, $value, $grou
* list is empty, validation succeeded.
*
* @api
+ *
+ * @deprecated Renamed to {@link Validator\ValidatorInterface::validate()}
+ * in Symfony 2.5. Will be removed in Symfony 3.0.
*/
public function validateValue($value, $constraints, $groups = null);
@@ -94,6 +104,11 @@ public function validateValue($value, $constraints, $groups = null);
* @return MetadataFactoryInterface The metadata factory.
*
* @api
+ *
+ * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0.
+ * Use {@link Validator\ValidatorInterface::getMetadataFor()} or
+ * {@link Validator\ValidatorInterface::hasMetadataFor()}
+ * instead.
*/
public function getMetadataFactory();
}
diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php
new file mode 100644
index 0000000000000..d5905a0a5cbcb
--- /dev/null
+++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php
@@ -0,0 +1,201 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Violation;
+
+use Symfony\Component\Translation\TranslatorInterface;
+use Symfony\Component\Validator\ConstraintViolation;
+use Symfony\Component\Validator\ConstraintViolationList;
+use Symfony\Component\Validator\Util\PropertyPath;
+
+/**
+ * Default implementation of {@link ConstraintViolationBuilderInterface}.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ *
+ * @internal You should not instantiate or use this class. Code against
+ * {@link ConstraintViolationBuilderInterface} instead.
+ */
+class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
+{
+ /**
+ * @var ConstraintViolationList
+ */
+ private $violations;
+
+ /**
+ * @var string
+ */
+ private $message;
+
+ /**
+ * @var array
+ */
+ private $parameters;
+
+ /**
+ * @var mixed
+ */
+ private $root;
+
+ /**
+ * @var mixed
+ */
+ private $invalidValue;
+
+ /**
+ * @var string
+ */
+ private $propertyPath;
+
+ /**
+ * @var TranslatorInterface
+ */
+ private $translator;
+
+ /**
+ * @var string|null
+ */
+ private $translationDomain;
+
+ /**
+ * @var integer|null
+ */
+ private $plural;
+
+ /**
+ * @var mixed
+ */
+ private $code;
+
+ public function __construct(ConstraintViolationList $violations, $message, array $parameters, $root, $propertyPath, $invalidValue, TranslatorInterface $translator, $translationDomain = null)
+ {
+ $this->violations = $violations;
+ $this->message = $message;
+ $this->parameters = $parameters;
+ $this->root = $root;
+ $this->propertyPath = $propertyPath;
+ $this->invalidValue = $invalidValue;
+ $this->translator = $translator;
+ $this->translationDomain = $translationDomain;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function atPath($path)
+ {
+ $this->propertyPath = PropertyPath::append($this->propertyPath, $path);
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setParameter($key, $value)
+ {
+ $this->parameters[$key] = $value;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setParameters(array $parameters)
+ {
+ $this->parameters = $parameters;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setTranslationDomain($translationDomain)
+ {
+ $this->translationDomain = $translationDomain;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setInvalidValue($invalidValue)
+ {
+ $this->invalidValue = $invalidValue;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setPlural($number)
+ {
+ $this->plural = $number;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setCode($code)
+ {
+ $this->code = $code;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addViolation()
+ {
+ if (null === $this->plural) {
+ $translatedMessage = $this->translator->trans(
+ $this->message,
+ $this->parameters,
+ $this->translationDomain
+ );
+ } else {
+ try {
+ $translatedMessage = $this->translator->transChoice(
+ $this->message,
+ $this->plural,
+ $this->parameters,
+ $this->translationDomain#
+ );
+ } catch (\InvalidArgumentException $e) {
+ $translatedMessage = $this->translator->trans(
+ $this->message,
+ $this->parameters,
+ $this->translationDomain
+ );
+ }
+ }
+
+ $this->violations->add(new ConstraintViolation(
+ $translatedMessage,
+ $this->message,
+ $this->parameters,
+ $this->root,
+ $this->propertyPath,
+ $this->invalidValue,
+ $this->plural,
+ $this->code
+ ));
+ }
+}
diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php
new file mode 100644
index 0000000000000..c522860aa75e2
--- /dev/null
+++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilderInterface.php
@@ -0,0 +1,108 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Validator\Violation;
+
+/**
+ * Builds {@link \Symfony\Component\Validator\ConstraintViolationInterface}
+ * objects.
+ *
+ * Use the various methods on this interface to configure the built violation.
+ * Finally, call {@link addViolation()} to add the violation to the current
+ * execution context.
+ *
+ * @since 2.5
+ * @author Bernhard Schussek
+ */
+interface ConstraintViolationBuilderInterface
+{
+ /**
+ * Stores the property path at which the violation should be generated.
+ *
+ * The passed path will be appended to the current property path of the
+ * execution context.
+ *
+ * @param string $path The property path
+ *
+ * @return ConstraintViolationBuilderInterface This builder
+ */
+ public function atPath($path);
+
+ /**
+ * Sets a parameter to be inserted into the violation message.
+ *
+ * @param string $key The name of the parameter
+ * @param string $value The value to be inserted in the parameter's place
+ *
+ * @return ConstraintViolationBuilderInterface This builder
+ */
+ public function setParameter($key, $value);
+
+ /**
+ * Sets all parameters to be inserted into the violation message.
+ *
+ * @param array $parameters An array with the parameter names as keys and
+ * the values to be inserted in their place as
+ * values
+ *
+ * @return ConstraintViolationBuilderInterface This builder
+ */
+ public function setParameters(array $parameters);
+
+ /**
+ * Sets the translation domain which should be used for translating the
+ * violation message.
+ *
+ * @param string $translationDomain The translation domain
+ *
+ * @return ConstraintViolationBuilderInterface This builder
+ *
+ * @see \Symfony\Component\Translation\TranslatorInterface
+ */
+ public function setTranslationDomain($translationDomain);
+
+ /**
+ * Sets the invalid value that caused this violation.
+ *
+ * @param mixed $invalidValue The invalid value
+ *
+ * @return ConstraintViolationBuilderInterface This builder
+ */
+ public function setInvalidValue($invalidValue);
+
+ /**
+ * Sets the number which determines how the plural form of the violation
+ * message is chosen when it is translated.
+ *
+ * @param integer $number The number for determining the plural form
+ *
+ * @return ConstraintViolationBuilderInterface This builder
+ *
+ * @see \Symfony\Component\Translation\TranslatorInterface::transChoice()
+ */
+ public function setPlural($number);
+
+ /**
+ * Sets the violation code.
+ *
+ * @param mixed $code The violation code
+ *
+ * @return ConstraintViolationBuilderInterface This builder
+ *
+ * @internal This method is internal and should not be used by user code
+ */
+ public function setCode($code);
+
+ /**
+ * Adds the violation to the current execution context.
+ */
+ public function addViolation();
+}
diff --git a/src/Symfony/Component/Validator/composer.json b/src/Symfony/Component/Validator/composer.json
index 2e97389ca51e9..de88ca40d8741 100644
--- a/src/Symfony/Component/Validator/composer.json
+++ b/src/Symfony/Component/Validator/composer.json
@@ -17,14 +17,14 @@
],
"require": {
"php": ">=5.3.3",
- "symfony/translation": "~2.0",
- "symfony/property-access": "~2.2"
+ "symfony/translation": "~2.0"
},
"require-dev": {
"symfony/http-foundation": "~2.1",
"symfony/intl": "~2.3",
"symfony/yaml": "~2.0",
"symfony/config": "~2.2",
+ "symfony/property-access": "~2.2",
"doctrine/annotations": "~1.0",
"doctrine/cache": "~1.0",
"egulias/email-validator": "~1.0"
@@ -37,6 +37,7 @@
"symfony/yaml": "",
"symfony/config": "",
"egulias/email-validator": "Strict (RFC compliant) email validation"
+ "symfony/property-access": "For using the 2.4 Validator API"
},
"autoload": {
"psr-0": { "Symfony\\Component\\Validator\\": "" }