diff --git a/src/Symfony/Component/Validator/ConstraintValidator.php b/src/Symfony/Component/Validator/ConstraintValidator.php index 07373fc4a4..d0b60b5ede 100644 --- a/src/Symfony/Component/Validator/ConstraintValidator.php +++ b/src/Symfony/Component/Validator/ConstraintValidator.php @@ -20,7 +20,7 @@ abstract class ConstraintValidator implements ConstraintValidatorInterface /** * {@inheritDoc} */ - public function initialize(ValidationContext $context) + public function initialize(ExecutionContext $context) { $this->context = $context; $this->messageTemplate = ''; diff --git a/src/Symfony/Component/Validator/ConstraintValidatorInterface.php b/src/Symfony/Component/Validator/ConstraintValidatorInterface.php index 4b423be965..ef1d419747 100644 --- a/src/Symfony/Component/Validator/ConstraintValidatorInterface.php +++ b/src/Symfony/Component/Validator/ConstraintValidatorInterface.php @@ -16,9 +16,9 @@ interface ConstraintValidatorInterface /** * Initialize the constraint validator. * - * @param ValidationContext $context The current validation context + * @param ExecutionContext $context The current validation context */ - function initialize(ValidationContext $context); + function initialize(ExecutionContext $context); /** * @param mixed $value The value that should be validated diff --git a/src/Symfony/Component/Validator/ValidationContext.php b/src/Symfony/Component/Validator/ExecutionContext.php similarity index 99% rename from src/Symfony/Component/Validator/ValidationContext.php rename to src/Symfony/Component/Validator/ExecutionContext.php index 8f9117a4fd..009d89229b 100644 --- a/src/Symfony/Component/Validator/ValidationContext.php +++ b/src/Symfony/Component/Validator/ExecutionContext.php @@ -22,7 +22,7 @@ use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface; * @author Fabien Potencier * @author Bernhard Schussek */ -class ValidationContext +class ExecutionContext { protected $root; protected $propertyPath; diff --git a/src/Symfony/Component/Validator/GraphWalker.php b/src/Symfony/Component/Validator/GraphWalker.php index e1866543a1..9038cd4cb0 100644 --- a/src/Symfony/Component/Validator/GraphWalker.php +++ b/src/Symfony/Component/Validator/GraphWalker.php @@ -35,7 +35,7 @@ class GraphWalker public function __construct($root, ClassMetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $factory) { - $this->context = new ValidationContext($root, $this, $metadataFactory); + $this->context = new ExecutionContext($root, $this, $metadataFactory); $this->validatorFactory = $factory; $this->metadataFactory = $metadataFactory; } diff --git a/src/Symfony/Component/Validator/ValidatorContext.php b/src/Symfony/Component/Validator/ValidatorContext.php new file mode 100644 index 0000000000..0ba2565a26 --- /dev/null +++ b/src/Symfony/Component/Validator/ValidatorContext.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface; + +/** + * Default implementaton of ValidatorContextInterface + * + * @author Bernhard Schussek + */ +class ValidatorContext implements ValidatorContextInterface +{ + /** + * The class metadata factory used in the new validator + * @var ClassMetadataFactoryInterface + */ + protected $classMetadataFactory = null; + + /** + * The constraint validator factory used in the new validator + * @var ConstraintValidatorFactoryInterface + */ + protected $constraintValidatorFactory = null; + + /** + * @inheritDoc + */ + public function classMetadataFactory(ClassMetadataFactoryInterface $classMetadataFactory) + { + $this->classMetadataFactory = $classMetadataFactory; + + return $this; + } + + /** + * @inheritDoc + */ + public function constraintValidatorFactory(ConstraintValidatorFactoryInterface $constraintValidatorFactory) + { + $this->constraintValidatorFactory = $constraintValidatorFactory; + + return $this; + } + + /** + * @inheritDoc + */ + public function getValidator() + { + return new Validator( + $this->classMetadataFactory, + $this->constraintValidatorFactory + ); + } + + /** + * Returns the class metadata factory used in the new validator + * + * @return ClassMetadataFactoryInterface The factory instance + */ + public function getClassMetadataFactory() + { + return $this->classMetadataFactory; + } + + /** + * Returns the constraint validator factory used in the new validator + * + * @return ConstraintValidatorFactoryInterface The factory instance + */ + public function getConstraintValidatorFactory() + { + return $this->constraintValidatorFactory; + } +} diff --git a/src/Symfony/Component/Validator/ValidatorContextInterface.php b/src/Symfony/Component/Validator/ValidatorContextInterface.php new file mode 100644 index 0000000000..814b2aa03a --- /dev/null +++ b/src/Symfony/Component/Validator/ValidatorContextInterface.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface; + +/** + * Stores settings for creating a new validator and creates validators + * + * The methods in this class are chainable, i.e. they return the context + * object itself. When you have finished configuring the new validator, call + * getValidator() to create the it. + * + * + * $validator = $context + * ->classMetadataFactory($customFactory) + * ->getValidator(); + * + * + * @author Bernhard Schussek + */ +interface ValidatorContextInterface +{ + /** + * Sets the class metadata factory used in the new validator + * + * @param ClassMetadataFactoryInterface $classMetadataFactory The factory instance + */ + function classMetadataFactory(ClassMetadataFactoryInterface $classMetadataFactory); + + /** + * Sets the constraint validator factory used in the new validator + * + * @param ConstraintValidatorFactoryInterface $constraintValidatorFactory The factory instance + */ + function constraintValidatorFactory(ConstraintValidatorFactoryInterface $constraintValidatorFactory); + + /** + * Creates a new validator with the settings stored in this context + * + * @return ValidatorInterface The new validator + */ + function getValidator(); +} \ No newline at end of file diff --git a/src/Symfony/Component/Validator/ValidatorFactory.php b/src/Symfony/Component/Validator/ValidatorFactory.php new file mode 100644 index 0000000000..495c92f525 --- /dev/null +++ b/src/Symfony/Component/Validator/ValidatorFactory.php @@ -0,0 +1,199 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Validator\Exception\MappingException; +use Symfony\Component\Validator\Mapping\ClassMetadataFactory; +use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface; +use Symfony\Component\Validator\Mapping\Loader\XmlFilesLoader; +use Symfony\Component\Validator\Mapping\Loader\YamlFilesLoader; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; +use Symfony\Component\Validator\Mapping\Loader\LoaderChain; + +/** + * Creates and configures new validator objects + * + * Usually you will use the static method buildDefault() to initialize a + * factory with default configuration. To this method you can pass various + * parameters that configure where the validator mapping is found. If you + * don't pass a parameter, the mapping will be read from annotations. + * + * + * // read from annotations only + * $factory = ValidatorFactory::buildDefault(); + * + * // read from XML and YAML, suppress annotations + * $factory = ValidatorFactory::buildDefault(array( + * '/path/to/mapping.xml', + * '/path/to/other/mapping.yml', + * ), false); + * + * + * You then have to call getValidator() to create new validators. + * + * + * $validator = $factory->getValidator(); + * + * + * When manually constructing a factory, the default configuration of the + * validators can be passed to the constructor as a ValidatorContextInterface + * object. + * + * + * $defaultContext = new ValidatorContext(); + * $defaultContext->classMetadataFactory($metadataFactory); + * $defaultContext->constraintValidatorFactory($validatorFactory); + * $factory = new ValidatorFactory($defaultContext); + * + * $form = $factory->getValidator(); + * + * + * You can also override the default configuration by calling any of the + * methods in this class. These methods return a ValidatorContextInterface object + * on which you can override further settings or call getValidator() to create + * a form. + * + * + * $form = $factory + * ->classMetadataFactory($customFactory); + * ->getValidator(); + * + * + * ValidatorFactory instances should be cached and reused in your application. + * + * @author Bernhard Schussek + */ +class ValidatorFactory implements ValidatorContextInterface +{ + /** + * Holds the context with the default configuration + * @var ValidatorContextInterface + */ + protected $defaultContext; + + /** + * Builds a validator factory with the default mapping loaders + * + * @param array $mappingFiles A list of XML or YAML file names + * where mapping information can be + * found. Can be empty. + * @param boolean $annotations Whether to use annotations for + * retrieving mapping information + * @param array $annotationNamespaces The annotation namespaces used + * for finding the annotation classes. + * The namespace "validation" is used + * by default + * @param string $staticMethod The name of the static method to + * use, if static method loading should + * be enabled + * @throws \InvalidArgumentException If any of the files in $mappingFiles + * has neither the extension ".xml" nor + * ".yml" nor ".yaml" + */ + public static function buildDefault(array $mappingFiles = array(), $annotations = true, $annotationNamespaces = null, $staticMethod = null) + { + $xmlMappingFiles = array(); + $yamlMappingFiles = array(); + $loaders = array(); + $context = new ValidatorContext(); + + foreach ($mappingFiles as $file) { + $extension = pathinfo($file, PATHINFO_EXTENSION); + + if ($extension === 'xml') { + $xmlMappingFiles[] = $file; + } else if ($extension === 'yaml' || $extension === 'yml') { + $yamlMappingFiles[] = $file; + } else { + throw new MappingException('The only supported mapping file formats are XML and YAML'); + } + } + + if (count($xmlMappingFiles) > 0) { + $loaders[] = new XmlFilesLoader($xmlMappingFiles); + } + + if (count($yamlMappingFiles) > 0) { + $loaders[] = new YamlFilesLoader($yamlMappingFiles); + } + + if ($annotations) { + $loaders[] = new AnnotationLoader($annotationNamespaces); + } + + if ($staticMethod) { + $loaders[] = new StaticMethodLoader($staticMethod); + } + + if (count($loaders) > 1) { + $loader = new LoaderChain($loaders); + } else if (count($loaders) === 1) { + $loader = $loaders[0]; + } else { + throw new MappingException('No mapping loader was found for the given parameters'); + } + + $context->classMetadataFactory(new ClassMetadataFactory($loader)); + $context->constraintValidatorFactory(new ConstraintValidatorFactory()); + + return new static($context); + } + + /** + * Sets the given context as default context + * + * @param ValidatorContextInterface $defaultContext A preconfigured context + */ + public function __construct(ValidatorContextInterface $defaultContext = null) + { + $this->defaultContext = null === $defaultContext ? new ValidatorContext() : $defaultContext; + } + + /** + * Overrides the class metadata factory of the default context and returns + * the new context + * + * @param ClassMetadataFactoryInterface $metadataFactory The new factory instance + * @return ValidatorContextInterface The preconfigured form context + */ + public function classMetadataFactory(ClassMetadataFactoryInterface $metadataFactory) + { + $context = clone $this->defaultContext; + + return $context->classMetadataFactory($metadataFactory); + } + + /** + * Overrides the constraint validator factory of the default context and + * returns the new context + * + * @param ClassMetadataFactoryInterface $validatorFactory The new factory instance + * @return ValidatorContextInterface The preconfigured form context + */ + public function constraintValidatorFactory(ConstraintValidatorFactoryInterface $validatorFactory) + { + $context = clone $this->defaultContext; + + return $context->constraintValidatorFactory($validatorFactory); + } + + /** + * Creates a new validator with the settings stored in the default context + * + * @return ValidatorInterface The new validator + */ + public function getValidator() + { + return $this->defaultContext->getValidator(); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Validator/Constraints/AllValidatorTest.php b/tests/Symfony/Tests/Component/Validator/Constraints/AllValidatorTest.php index 152374534a..16acf1c581 100644 --- a/tests/Symfony/Tests/Component/Validator/Constraints/AllValidatorTest.php +++ b/tests/Symfony/Tests/Component/Validator/Constraints/AllValidatorTest.php @@ -11,7 +11,7 @@ namespace Symfony\Tests\Component\Validator; -use Symfony\Component\Validator\ValidationContext; +use Symfony\Component\Validator\ExecutionContext; use Symfony\Component\Validator\Constraints\Min; use Symfony\Component\Validator\Constraints\All; use Symfony\Component\Validator\Constraints\AllValidator; @@ -27,7 +27,7 @@ class AllValidatorTest extends \PHPUnit_Framework_TestCase $this->walker = $this->getMock('Symfony\Component\Validator\GraphWalker', array(), array(), '', false); $metadataFactory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface'); - $this->context = new ValidationContext('Root', $this->walker, $metadataFactory); + $this->context = new ExecutionContext('Root', $this->walker, $metadataFactory); $this->validator = new AllValidator(); $this->validator->initialize($this->context); diff --git a/tests/Symfony/Tests/Component/Validator/Constraints/ChoiceValidatorTest.php b/tests/Symfony/Tests/Component/Validator/Constraints/ChoiceValidatorTest.php index 14478624d7..1d3775c0ca 100644 --- a/tests/Symfony/Tests/Component/Validator/Constraints/ChoiceValidatorTest.php +++ b/tests/Symfony/Tests/Component/Validator/Constraints/ChoiceValidatorTest.php @@ -11,7 +11,7 @@ namespace Symfony\Tests\Component\Validator; -use Symfony\Component\Validator\ValidationContext; +use Symfony\Component\Validator\ExecutionContext; use Symfony\Component\Validator\Constraints\Choice; use Symfony\Component\Validator\Constraints\ChoiceValidator; @@ -33,7 +33,7 @@ class ChoiceValidatorTest extends \PHPUnit_Framework_TestCase { $walker = $this->getMock('Symfony\Component\Validator\GraphWalker', array(), array(), '', false); $factory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface'); - $context = new ValidationContext('root', $walker, $factory); + $context = new ExecutionContext('root', $walker, $factory); $context->setCurrentClass(__CLASS__); $this->validator = new ChoiceValidator(); $this->validator->initialize($context); diff --git a/tests/Symfony/Tests/Component/Validator/Constraints/CollectionValidatorTest.php b/tests/Symfony/Tests/Component/Validator/Constraints/CollectionValidatorTest.php index eea8cfea40..bba438f5eb 100644 --- a/tests/Symfony/Tests/Component/Validator/Constraints/CollectionValidatorTest.php +++ b/tests/Symfony/Tests/Component/Validator/Constraints/CollectionValidatorTest.php @@ -11,7 +11,7 @@ namespace Symfony\Tests\Component\Validator; -use Symfony\Component\Validator\ValidationContext; +use Symfony\Component\Validator\ExecutionContext; use Symfony\Component\Validator\Constraints\Min; use Symfony\Component\Validator\Constraints\Collection; use Symfony\Component\Validator\Constraints\CollectionValidator; @@ -27,7 +27,7 @@ class CollectionValidatorTest extends \PHPUnit_Framework_TestCase $this->walker = $this->getMock('Symfony\Component\Validator\GraphWalker', array(), array(), '', false); $metadataFactory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface'); - $this->context = new ValidationContext('Root', $this->walker, $metadataFactory); + $this->context = new ExecutionContext('Root', $this->walker, $metadataFactory); $this->validator = new CollectionValidator(); $this->validator->initialize($this->context); diff --git a/tests/Symfony/Tests/Component/Validator/ValidatorContextTest.php b/tests/Symfony/Tests/Component/Validator/ValidatorContextTest.php new file mode 100644 index 0000000000..74fc5dd2f5 --- /dev/null +++ b/tests/Symfony/Tests/Component/Validator/ValidatorContextTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Validator; + +use Symfony\Component\Validator\Validator; +use Symfony\Component\Validator\ValidatorContext; + +class ValidatorContextTest extends \PHPUnit_Framework_TestCase +{ + protected $context; + + protected function setUp() + { + $this->context = new ValidatorContext(); + } + + public function testSetClassMetadataFactory() + { + $factory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface'); + $result = $this->context->classMetadataFactory($factory); + + $this->assertSame($this->context, $result); + $this->assertSame($factory, $this->context->getClassMetadataFactory()); + } + + public function testSetConstraintValidatorFactory() + { + $factory = $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface'); + $result = $this->context->constraintValidatorFactory($factory); + + $this->assertSame($this->context, $result); + $this->assertSame($factory, $this->context->getConstraintValidatorFactory()); + } + + public function testGetValidator() + { + $metadataFactory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface'); + $validatorFactory = $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface'); + + $validator = $this->context + ->classMetadataFactory($metadataFactory) + ->constraintValidatorFactory($validatorFactory) + ->getValidator(); + + $this->assertEquals(new Validator($metadataFactory, $validatorFactory), $validator); + } +} \ No newline at end of file diff --git a/tests/Symfony/Tests/Component/Validator/ValidatorFactoryTest.php b/tests/Symfony/Tests/Component/Validator/ValidatorFactoryTest.php new file mode 100644 index 0000000000..bd0fbc2eb6 --- /dev/null +++ b/tests/Symfony/Tests/Component/Validator/ValidatorFactoryTest.php @@ -0,0 +1,181 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Tests\Component\Validator; + +use Symfony\Component\Validator\Validator; +use Symfony\Component\Validator\ValidatorContext; +use Symfony\Component\Validator\ValidatorFactory; +use Symfony\Component\Validator\ConstraintValidatorFactory; +use Symfony\Component\Validator\Mapping\ClassMetadataFactory; +use Symfony\Component\Validator\Mapping\Loader\XmlFilesLoader; +use Symfony\Component\Validator\Mapping\Loader\YamlFilesLoader; +use Symfony\Component\Validator\Mapping\Loader\AnnotationLoader; +use Symfony\Component\Validator\Mapping\Loader\StaticMethodLoader; +use Symfony\Component\Validator\Mapping\Loader\LoaderChain; + +class ValidatorFactoryTest extends \PHPUnit_Framework_TestCase +{ + protected $defaultContext; + protected $factory; + + protected function setUp() + { + $this->defaultContext = new ValidatorContext(); + $this->factory = new ValidatorFactory($this->defaultContext); + } + + public function testOverrideClassMetadataFactory() + { + $factory1 = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface'); + $factory2 = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface'); + + $this->defaultContext->classMetadataFactory($factory1); + + $result = $this->factory->classMetadataFactory($factory2); + + $this->assertSame($factory1, $this->defaultContext->getClassMetadataFactory()); + $this->assertSame($factory2, $result->getClassMetadataFactory()); + } + + public function testOverrideConstraintValidatorFactory() + { + $factory1 = $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface'); + $factory2 = $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface'); + + $this->defaultContext->constraintValidatorFactory($factory1); + + $result = $this->factory->constraintValidatorFactory($factory2); + + $this->assertSame($factory1, $this->defaultContext->getConstraintValidatorFactory()); + $this->assertSame($factory2, $result->getConstraintValidatorFactory()); + } + + public function testGetValidator() + { + $metadataFactory = $this->getMock('Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface'); + $validatorFactory = $this->getMock('Symfony\Component\Validator\ConstraintValidatorFactoryInterface'); + + $this->defaultContext + ->classMetadataFactory($metadataFactory) + ->constraintValidatorFactory($validatorFactory); + + $validator = $this->factory->getValidator(); + + $this->assertEquals(new Validator($metadataFactory, $validatorFactory), $validator); + } + + public function testBuildDefaultFromAnnotations() + { + $factory = ValidatorFactory::buildDefault(); + + $context = new ValidatorContext(); + $context + ->classMetadataFactory(new ClassMetadataFactory(new AnnotationLoader())) + ->constraintValidatorFactory(new ConstraintValidatorFactory()); + + $this->assertEquals(new ValidatorFactory($context), $factory); + } + + public function testBuildDefaultFromAnnotationsWithCustomNamespaces() + { + $factory = ValidatorFactory::buildDefault(array(), true, array( + 'myns' => 'My\\Namespace\\', + )); + + $context = new ValidatorContext(); + $context + ->classMetadataFactory(new ClassMetadataFactory(new AnnotationLoader(array( + 'myns' => 'My\\Namespace\\', + )))) + ->constraintValidatorFactory(new ConstraintValidatorFactory()); + + $this->assertEquals(new ValidatorFactory($context), $factory); + } + + public function testBuildDefaultFromXml() + { + $path = __DIR__.'/Mapping/Loader/constraint-mapping.xml'; + $factory = ValidatorFactory::buildDefault(array($path), false); + + $context = new ValidatorContext(); + $context + ->classMetadataFactory(new ClassMetadataFactory(new XmlFilesLoader(array($path)))) + ->constraintValidatorFactory(new ConstraintValidatorFactory()); + + $this->assertEquals(new ValidatorFactory($context), $factory); + } + + public function testBuildDefaultFromYaml() + { + $path = __DIR__.'/Mapping/Loader/constraint-mapping.yml'; + $factory = ValidatorFactory::buildDefault(array($path), false); + + $context = new ValidatorContext(); + $context + ->classMetadataFactory(new ClassMetadataFactory(new YamlFilesLoader(array($path)))) + ->constraintValidatorFactory(new ConstraintValidatorFactory()); + + $this->assertEquals(new ValidatorFactory($context), $factory); + } + + public function testBuildDefaultFromStaticMethod() + { + $path = __DIR__.'/Mapping/Loader/constraint-mapping.yml'; + $factory = ValidatorFactory::buildDefault(array(), false, null, 'loadMetadata'); + + $context = new ValidatorContext(); + $context + ->classMetadataFactory(new ClassMetadataFactory(new StaticMethodLoader('loadMetadata'))) + ->constraintValidatorFactory(new ConstraintValidatorFactory()); + + $this->assertEquals(new ValidatorFactory($context), $factory); + } + + public function testBuildDefaultFromMultipleLoaders() + { + $xmlPath = __DIR__.'/Mapping/Loader/constraint-mapping.xml'; + $yamlPath = __DIR__.'/Mapping/Loader/constraint-mapping.yml'; + $factory = ValidatorFactory::buildDefault(array($xmlPath, $yamlPath), true, null, 'loadMetadata'); + + $chain = new LoaderChain(array( + new XmlFilesLoader(array($xmlPath)), + new YamlFilesLoader(array($yamlPath)), + new AnnotationLoader(), + new StaticMethodLoader('loadMetadata'), + )); + + $context = new ValidatorContext(); + $context + ->classMetadataFactory(new ClassMetadataFactory($chain)) + ->constraintValidatorFactory(new ConstraintValidatorFactory()); + + $this->assertEquals(new ValidatorFactory($context), $factory); + } + + /** + * @expectedException Symfony\Component\Validator\Exception\MappingException + */ + public function testBuildDefaultThrowsExceptionIfNoLoaderIsFound() + { + ValidatorFactory::buildDefault(array(), false); + } + + /** + * @expectedException Symfony\Component\Validator\Exception\MappingException + */ + public function testBuildDefaultThrowsExceptionIfUnknownExtension() + { + ValidatorFactory::buildDefault(array( + __DIR__.'/Mapping/Loader/StaticMethodLoaderTest.php' + )); + } +} \ No newline at end of file