[Validator] Added ValidatorFactory for programmatically creating validators

This commit is contained in:
Bernhard Schussek 2011-01-19 15:02:00 +01:00
parent 8f8f53d631
commit 6ad22fd702
12 changed files with 585 additions and 11 deletions

View File

@ -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 = '';

View File

@ -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

View File

@ -22,7 +22,7 @@ use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface;
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
class ValidationContext
class ExecutionContext
{
protected $root;
protected $propertyPath;

View File

@ -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;
}

View File

@ -0,0 +1,85 @@
<?php
namespace Symfony\Component\Validator;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* 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 <bernhard.schussek@symfony-project.com>
*/
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;
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Symfony\Component\Validator;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* 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.
*
* <code>
* $validator = $context
* ->classMetadataFactory($customFactory)
* ->getValidator();
* </code>
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
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();
}

View File

@ -0,0 +1,199 @@
<?php
namespace Symfony\Component\Validator;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* 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.
*
* <code>
* // 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);
* </code>
*
* You then have to call getValidator() to create new validators.
*
* <code>
* $validator = $factory->getValidator();
* </code>
*
* When manually constructing a factory, the default configuration of the
* validators can be passed to the constructor as a ValidatorContextInterface
* object.
*
* <code>
* $defaultContext = new ValidatorContext();
* $defaultContext->classMetadataFactory($metadataFactory);
* $defaultContext->constraintValidatorFactory($validatorFactory);
* $factory = new ValidatorFactory($defaultContext);
*
* $form = $factory->getValidator();
* </code>
*
* 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.
*
* <code>
* $form = $factory
* ->classMetadataFactory($customFactory);
* ->getValidator();
* </code>
*
* ValidatorFactory instances should be cached and reused in your application.
*
* @author Bernhard Schussek <bernhard.schussek@symfony-project.com>
*/
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();
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* 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);
}
}

View File

@ -0,0 +1,181 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* 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'
));
}
}