Merge branch '2.5'

* 2.5:
  [Translator] Use quote to surround invalid locale
  [Validator] Fixed memory leak in ValidatorBuilder
  [FrameworkBundle] changed KernelTestCase::getKernelClass() to check $_SERVER['KERNEL_DIR'] before invoking getPhpUnitXmlDir()
  Optimize assertLocale regexp
  [ExpressionLanguage] Fixed an issue with # characters in double quoted string literals
  [Validator] Fixed object initializers in 2.5 version of the Validator
  Add some tweaks to the pt_BR translations
  [Validator] Backported #11410 to 2.3: Object initializers are called only once per object
  [Translator][FrameworkBundle] Added @ to the list of allowed chars in Translator
  [Process] Reduce I/O load on Windows platform
  [Form] Check if IntlDateFormatter constructor returned a valid object before using it

Conflicts:
	src/Symfony/Component/Process/ProcessPipes.php
This commit is contained in:
Fabien Potencier 2014-07-24 19:03:35 +02:00
commit 3b837dc5c1
22 changed files with 258 additions and 67 deletions

View File

@ -100,14 +100,17 @@ abstract class KernelTestCase extends \PHPUnit_Framework_TestCase
*/ */
protected static function getKernelClass() protected static function getKernelClass()
{ {
$dir = $phpUnitDir = static::getPhpUnitXmlDir();
if (isset($_SERVER['KERNEL_DIR'])) { if (isset($_SERVER['KERNEL_DIR'])) {
$dir = $_SERVER['KERNEL_DIR']; $dir = $_SERVER['KERNEL_DIR'];
if (!is_dir($dir) && is_dir("$phpUnitDir/$dir")) { if (!is_dir($dir)) {
$dir = "$phpUnitDir/$dir"; $phpUnitDir = static::getPhpUnitXmlDir();
if (is_dir("$phpUnitDir/$dir")) {
$dir = "$phpUnitDir/$dir";
}
} }
} else {
$dir = static::getPhpUnitXmlDir();
} }
$finder = new Finder(); $finder = new Finder();

View File

@ -46,7 +46,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
{ {
$translator = $this->getTranslator($this->getLoader()); $translator = $this->getTranslator($this->getLoader());
$translator->setLocale('fr'); $translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8')); $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar')); $this->assertEquals('bar (EN)', $translator->trans('bar'));
@ -56,6 +56,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
} }
public function testTransWithCaching() public function testTransWithCaching()
@ -63,7 +64,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
// prime the cache // prime the cache
$translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir)); $translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir));
$translator->setLocale('fr'); $translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8')); $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar')); $this->assertEquals('bar (EN)', $translator->trans('bar'));
@ -73,12 +74,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
// do it another time as the cache is primed now // do it another time as the cache is primed now
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir)); $translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir));
$translator->setLocale('fr'); $translator->setLocale('fr');
$translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8')); $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin'));
$this->assertEquals('foo (FR)', $translator->trans('foo')); $this->assertEquals('foo (FR)', $translator->trans('foo'));
$this->assertEquals('bar (EN)', $translator->trans('bar')); $this->assertEquals('bar (EN)', $translator->trans('bar'));
@ -88,6 +90,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
} }
/** /**
@ -189,6 +192,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
'foobarbaz' => 'foobarbaz (fr.UTF-8)', 'foobarbaz' => 'foobarbaz (fr.UTF-8)',
)))) ))))
; ;
$loader
->expects($this->at(6))
->method('load')
->will($this->returnValue($this->getCatalogue('sr@latin', array(
'foobarbax' => 'foobarbax (sr@latin)',
))))
;
return $loader; return $loader;
} }
@ -220,6 +230,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese $translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese
$translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian Portuguese $translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian Portuguese
$translator->addResource('loader', 'foo', 'fr.UTF-8'); $translator->addResource('loader', 'foo', 'fr.UTF-8');
$translator->addResource('loader', 'foo', 'sr@latin'); // Latin Serbian
return $translator; return $translator;
} }

View File

@ -69,7 +69,7 @@ class Lexer
$tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1); $tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
++$cursor; ++$cursor;
} elseif (preg_match('/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As', $expression, $match, null, $cursor)) { } elseif (preg_match('/"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As', $expression, $match, null, $cursor)) {
// strings // strings
$tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1); $tokens[] = new Token(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)), $cursor + 1);
$cursor += strlen($match[0]); $cursor += strlen($match[0]);

View File

@ -78,6 +78,14 @@ class LexerTest extends \PHPUnit_Framework_TestCase
array(new Token('operator', '..', 1)), array(new Token('operator', '..', 1)),
'..', '..',
), ),
array(
array(new Token('string', '#foo', 1)),
"'#foo'",
),
array(
array(new Token('string', '#foo', 1)),
'"#foo"',
),
); );
} }
} }

View File

@ -152,6 +152,8 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
* Returns a preconfigured IntlDateFormatter instance * Returns a preconfigured IntlDateFormatter instance
* *
* @return \IntlDateFormatter * @return \IntlDateFormatter
*
* @throws TransformationFailedException in case the date formatter can not be constructed.
*/ */
protected function getIntlDateFormatter() protected function getIntlDateFormatter()
{ {
@ -162,6 +164,12 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
$pattern = $this->pattern; $pattern = $this->pattern;
$intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $pattern); $intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $pattern);
// new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323
if (!$intlDateFormatter) {
throw new TransformationFailedException(intl_get_error_message(), intl_get_error_code());
}
$intlDateFormatter->setLenient(false); $intlDateFormatter->setLenient(false);
return $intlDateFormatter; return $intlDateFormatter;

View File

@ -77,6 +77,12 @@ class DateType extends AbstractType
$calendar, $calendar,
$pattern $pattern
); );
// new \intlDateFormatter may return null instead of false in case of failure, see https://bugs.php.net/bug.php?id=66323
if (!$formatter) {
throw new InvalidOptionsException(intl_get_error_message(), intl_get_error_code());
}
$formatter->setLenient(false); $formatter->setLenient(false);
if ('choice' === $options['widget']) { if ('choice' === $options['widget']) {

View File

@ -673,7 +673,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
} }
$duration = microtime(true) - $start; $duration = microtime(true) - $start;
$this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration); if (defined('PHP_WINDOWS_VERSION_BUILD')) {
// Windows is a bit slower as it read file handles, then allow twice the precision
$maxDuration = $timeout + 2 * Process::TIMEOUT_PRECISION;
} else {
$maxDuration = $timeout + Process::TIMEOUT_PRECISION;
}
$this->assertLessThan($maxDuration, $duration);
} }
public function testCheckTimeoutOnNonStartedProcess() public function testCheckTimeoutOnNonStartedProcess()

View File

@ -103,7 +103,6 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('bar (fr)', $translator->trans('bar')); $this->assertEquals('bar (fr)', $translator->trans('bar'));
} }
/** /**
* @dataProvider getInvalidLocalesTests * @dataProvider getInvalidLocalesTests
* @expectedException \InvalidArgumentException * @expectedException \InvalidArgumentException
@ -329,7 +328,6 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
// no assertion. this method just asserts that no exception is thrown // no assertion. this method just asserts that no exception is thrown
} }
public function getTransFileTests() public function getTransFileTests()
{ {
return array( return array(
@ -430,6 +428,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
array('fr_FR'), array('fr_FR'),
array('fr.FR'), array('fr.FR'),
array('fr-FR.UTF8'), array('fr-FR.UTF8'),
array('sr@latin'),
); );
} }

View File

@ -318,8 +318,8 @@ class Translator implements TranslatorInterface
*/ */
private function assertValidLocale($locale) private function assertValidLocale($locale)
{ {
if (0 !== preg_match('/[^a-z0-9_\\.\\-]+/i', $locale, $match)) { if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
throw new \InvalidArgumentException(sprintf('Invalid locale: %s.', $locale)); throw new \InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
} }
} }
} }

View File

@ -18,6 +18,7 @@ use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Exception\BadMethodCallException; use Symfony\Component\Validator\Exception\BadMethodCallException;
use Symfony\Component\Validator\Mapping\MetadataInterface; use Symfony\Component\Validator\Mapping\MetadataInterface;
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
use Symfony\Component\Validator\ObjectInitializerInterface;
use Symfony\Component\Validator\Util\PropertyPath; use Symfony\Component\Validator\Util\PropertyPath;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder; use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
@ -113,6 +114,13 @@ class ExecutionContext implements ExecutionContextInterface
*/ */
private $validatedConstraints = array(); private $validatedConstraints = array();
/**
* Stores which objects have been initialized.
*
* @var array
*/
private $initializedObjects;
/** /**
* Creates a new execution context. * Creates a new execution context.
* *
@ -360,4 +368,36 @@ class ExecutionContext implements ExecutionContextInterface
{ {
return isset($this->validatedConstraints[$cacheKey.':'.$constraintHash]); return isset($this->validatedConstraints[$cacheKey.':'.$constraintHash]);
} }
/**
* Marks that an object was initialized.
*
* @param string $cacheKey The hash of the object
*
* @internal Used by the validator engine. Should not be called by user
* code.
*
* @see ObjectInitializerInterface
*/
public function markObjectAsInitialized($cacheKey)
{
$this->initializedObjects[$cacheKey] = true;
}
/**
* Returns whether an object was initialized.
*
* @param string $cacheKey The hash of the object
*
* @return bool Whether the object was already initialized
*
* @internal Used by the validator engine. Should not be called by user
* code.
*
* @see ObjectInitializerInterface
*/
public function isObjectInitialized($cacheKey)
{
return isset($this->initializedObjects[$cacheKey]);
}
} }

View File

@ -280,11 +280,11 @@
</trans-unit> </trans-unit>
<trans-unit id="73"> <trans-unit id="73">
<source>The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.</source> <source>The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.</source>
<target>O formato da imagem é muito grande ({{ ratio }}). O formato máximo é {{ max_ratio }}.</target> <target>A proporção da imagem é muito grande ({{ ratio }}). A proporção máxima permitida é {{ max_ratio }}.</target>
</trans-unit> </trans-unit>
<trans-unit id="74"> <trans-unit id="74">
<source>The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.</source> <source>The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.</source>
<target>O formato da imagem é muito pequeno ({{ ratio }}). O formato mínimo esperado é {{ min_ratio }}.</target> <target>A proporção da imagem é muito pequena ({{ ratio }}). A proporção mínima esperada é {{ min_ratio }}.</target>
</trans-unit> </trans-unit>
<trans-unit id="75"> <trans-unit id="75">
<source>The image is square ({{ width }}x{{ height }}px). Square images are not allowed.</source> <source>The image is square ({{ width }}x{{ height }}px). Square images are not allowed.</source>
@ -300,7 +300,7 @@
</trans-unit> </trans-unit>
<trans-unit id="78"> <trans-unit id="78">
<source>An empty file is not allowed.</source> <source>An empty file is not allowed.</source>
<target>Ficheiro vazio não é permitido.</target> <target>Arquivo vazio não é permitido.</target>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>

View File

@ -38,6 +38,7 @@ class Entity extends EntityParent implements EntityInterface
public $reference2; public $reference2;
private $internal; private $internal;
public $data = 'Overridden data'; public $data = 'Overridden data';
public $initialized = false;
public function __construct($internal = null) public function __construct($internal = null)
{ {

View File

@ -42,7 +42,7 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest
* *
* @return ValidatorInterface * @return ValidatorInterface
*/ */
abstract protected function createValidator(MetadataFactoryInterface $metadataFactory); abstract protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array());
protected function setUp() protected function setUp()
{ {
@ -678,4 +678,52 @@ abstract class Abstract2Dot5ApiTest extends AbstractValidatorTest
$this->assertTrue($called); $this->assertTrue($called);
} }
public function testInitializeObjectsOnFirstValidation()
{
$test = $this;
$entity = new Entity();
$entity->initialized = false;
// prepare initializers that set "initialized" to true
$initializer1 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface');
$initializer2 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface');
$initializer1->expects($this->once())
->method('initialize')
->with($entity)
->will($this->returnCallback(function ($object) {
$object->initialized = true;
}));
$initializer2->expects($this->once())
->method('initialize')
->with($entity);
$this->validator = $this->createValidator($this->metadataFactory, array(
$initializer1,
$initializer2
));
// prepare constraint which
// * checks that "initialized" is set to true
// * validates the object again
$callback = function ($object, ExecutionContextInterface $context) use ($test) {
$test->assertTrue($object->initialized);
// validate again in same group
$validator = $context->getValidator()->inContext($context);
$validator->validate($object);
// validate again in other group
$validator->validate($object, null, 'SomeGroup');
};
$this->metadata->addConstraint(new Callback($callback));
$this->validate($entity);
$this->assertTrue($entity->initialized);
}
} }

View File

@ -38,7 +38,7 @@ abstract class AbstractLegacyApiTest extends AbstractValidatorTest
* *
* @return LegacyValidatorInterface * @return LegacyValidatorInterface
*/ */
abstract protected function createValidator(MetadataFactoryInterface $metadataFactory); abstract protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array());
protected function setUp() protected function setUp()
{ {
@ -238,6 +238,52 @@ abstract class AbstractLegacyApiTest extends AbstractValidatorTest
$this->assertSame('Code', $violations[0]->getCode()); $this->assertSame('Code', $violations[0]->getCode());
} }
public function testInitializeObjectsOnFirstValidation()
{
$test = $this;
$entity = new Entity();
$entity->initialized = false;
// prepare initializers that set "initialized" to true
$initializer1 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface');
$initializer2 = $this->getMock('Symfony\\Component\\Validator\\ObjectInitializerInterface');
$initializer1->expects($this->once())
->method('initialize')
->with($entity)
->will($this->returnCallback(function ($object) {
$object->initialized = true;
}));
$initializer2->expects($this->once())
->method('initialize')
->with($entity);
$this->validator = $this->createValidator($this->metadataFactory, array(
$initializer1,
$initializer2
));
// prepare constraint which
// * checks that "initialized" is set to true
// * validates the object again
$callback = function ($object, ExecutionContextInterface $context) use ($test) {
$test->assertTrue($object->initialized);
// validate again in same group
$context->validate($object);
// validate again in other group
$context->validate($object, '', 'SomeGroup');
};
$this->metadata->addConstraint(new Callback($callback));
$this->validate($entity);
$this->assertTrue($entity->initialized);
}
public function testGetMetadataFactory() public function testGetMetadataFactory()
{ {
$this->assertSame($this->metadataFactory, $this->validator->getMetadataFactory()); $this->assertSame($this->metadataFactory, $this->validator->getMetadataFactory());

View File

@ -28,10 +28,10 @@ class LegacyValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
parent::setUp(); parent::setUp();
} }
protected function createValidator(MetadataFactoryInterface $metadataFactory) protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array())
{ {
$contextFactory = new LegacyExecutionContextFactory($metadataFactory, new DefaultTranslator()); $contextFactory = new LegacyExecutionContextFactory($metadataFactory, new DefaultTranslator());
return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory()); return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory(), $objectInitializers);
} }
} }

View File

@ -28,10 +28,10 @@ class LegacyValidatorLegacyApiTest extends AbstractLegacyApiTest
parent::setUp(); parent::setUp();
} }
protected function createValidator(MetadataFactoryInterface $metadataFactory) protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array())
{ {
$contextFactory = new LegacyExecutionContextFactory($metadataFactory, new DefaultTranslator()); $contextFactory = new LegacyExecutionContextFactory($metadataFactory, new DefaultTranslator());
return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory()); return new LegacyValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory(), $objectInitializers);
} }
} }

View File

@ -19,10 +19,10 @@ use Symfony\Component\Validator\Validator\RecursiveValidator;
class RecursiveValidator2Dot5ApiTest extends Abstract2Dot5ApiTest class RecursiveValidator2Dot5ApiTest extends Abstract2Dot5ApiTest
{ {
protected function createValidator(MetadataFactoryInterface $metadataFactory) protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array())
{ {
$contextFactory = new ExecutionContextFactory(new DefaultTranslator()); $contextFactory = new ExecutionContextFactory(new DefaultTranslator());
return new RecursiveValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory()); return new RecursiveValidator($contextFactory, $metadataFactory, new ConstraintValidatorFactory(), $objectInitializers);
} }
} }

View File

@ -21,9 +21,9 @@ use Symfony\Component\Validator\Validator as LegacyValidator;
class ValidatorTest extends AbstractLegacyApiTest class ValidatorTest extends AbstractLegacyApiTest
{ {
protected function createValidator(MetadataFactoryInterface $metadataFactory) protected function createValidator(MetadataFactoryInterface $metadataFactory, array $objectInitializers = array())
{ {
return new LegacyValidator($metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator()); return new LegacyValidator($metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator(), 'validators', $objectInitializers);
} }
/** /**

View File

@ -129,16 +129,19 @@ class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionCo
return; return;
} }
// Initialize if the object wasn't initialized before
if (!isset($this->validatedObjects[$hash])) {
foreach ($this->objectInitializers as $initializer) {
if (!$initializer instanceof ObjectInitializerInterface) {
throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.');
}
$initializer->initialize($value);
}
}
// Remember validating this object before starting and possibly // Remember validating this object before starting and possibly
// traversing the object graph // traversing the object graph
$this->validatedObjects[$hash][$group] = true; $this->validatedObjects[$hash][$group] = true;
foreach ($this->objectInitializers as $initializer) {
if (!$initializer instanceof ObjectInitializerInterface) {
throw new \LogicException('Validator initializers must implement ObjectInitializerInterface.');
}
$initializer->initialize($value);
}
} }
// Validate arrays recursively by default, otherwise every driver needs // Validate arrays recursively by default, otherwise every driver needs

View File

@ -27,6 +27,7 @@ use Symfony\Component\Validator\Mapping\MetadataInterface;
use Symfony\Component\Validator\Mapping\PropertyMetadataInterface; use Symfony\Component\Validator\Mapping\PropertyMetadataInterface;
use Symfony\Component\Validator\Mapping\TraversalStrategy; use Symfony\Component\Validator\Mapping\TraversalStrategy;
use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\ObjectInitializerInterface;
use Symfony\Component\Validator\Util\PropertyPath; use Symfony\Component\Validator\Util\PropertyPath;
/** /**
@ -52,23 +53,30 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
*/ */
private $validatorFactory; private $validatorFactory;
/**
* @var ObjectInitializerInterface[]
*/
private $objectInitializers;
/** /**
* Creates a validator for the given context. * Creates a validator for the given context.
* *
* @param ExecutionContextInterface $context The execution context * @param ExecutionContextInterface $context The execution context
* @param MetadataFactoryInterface $metadataFactory The factory for * @param MetadataFactoryInterface $metadataFactory The factory for
* fetching the metadata * fetching the metadata
* of validated objects * of validated objects
* @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
* constraint validators * constraint validators
* @param ObjectInitializerInterface[] $objectInitializers The object initializers
*/ */
public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory) public function __construct(ExecutionContextInterface $context, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = array())
{ {
$this->context = $context; $this->context = $context;
$this->defaultPropertyPath = $context->getPropertyPath(); $this->defaultPropertyPath = $context->getPropertyPath();
$this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP); $this->defaultGroups = array($context->getGroup() ?: Constraint::DEFAULT_GROUP);
$this->metadataFactory = $metadataFactory; $this->metadataFactory = $metadataFactory;
$this->validatorFactory = $validatorFactory; $this->validatorFactory = $validatorFactory;
$this->objectInitializers = $objectInitializers;
} }
/** /**
@ -432,6 +440,14 @@ class RecursiveContextualValidator implements ContextualValidatorInterface
{ {
$context->setNode($object, $object, $metadata, $propertyPath); $context->setNode($object, $object, $metadata, $propertyPath);
if (!$context->isObjectInitialized($cacheKey)) {
foreach ($this->objectInitializers as $initializer) {
$initializer->initialize($object);
}
$context->markObjectAsInitialized($cacheKey);
}
foreach ($groups as $key => $group) { foreach ($groups as $key => $group) {
// If the "Default" group is replaced by a group sequence, remember // If the "Default" group is replaced by a group sequence, remember
// to cascade the "Default" group when traversing the group // to cascade the "Default" group when traversing the group

View File

@ -15,6 +15,7 @@ use Symfony\Component\Validator\ConstraintValidatorFactoryInterface;
use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface; use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface; use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\ObjectInitializerInterface;
/** /**
* Recursive implementation of {@link ValidatorInterface}. * Recursive implementation of {@link ValidatorInterface}.
@ -39,22 +40,29 @@ class RecursiveValidator implements ValidatorInterface
*/ */
protected $validatorFactory; protected $validatorFactory;
/**
* @var ObjectInitializerInterface[]
*/
protected $objectInitializers;
/** /**
* Creates a new validator. * Creates a new validator.
* *
* @param ExecutionContextFactoryInterface $contextFactory The factory for * @param ExecutionContextFactoryInterface $contextFactory The factory for
* creating new contexts * creating new contexts
* @param MetadataFactoryInterface $metadataFactory The factory for * @param MetadataFactoryInterface $metadataFactory The factory for
* fetching the metadata * fetching the metadata
* of validated objects * of validated objects
* @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating * @param ConstraintValidatorFactoryInterface $validatorFactory The factory for creating
* constraint validators * constraint validators
* @param ObjectInitializerInterface[] $objectInitializers The object initializers
*/ */
public function __construct(ExecutionContextFactoryInterface $contextFactory, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory) public function __construct(ExecutionContextFactoryInterface $contextFactory, MetadataFactoryInterface $metadataFactory, ConstraintValidatorFactoryInterface $validatorFactory, array $objectInitializers = array())
{ {
$this->contextFactory = $contextFactory; $this->contextFactory = $contextFactory;
$this->metadataFactory = $metadataFactory; $this->metadataFactory = $metadataFactory;
$this->validatorFactory = $validatorFactory; $this->validatorFactory = $validatorFactory;
$this->objectInitializers = $objectInitializers;
} }
/** /**
@ -65,7 +73,8 @@ class RecursiveValidator implements ValidatorInterface
return new RecursiveContextualValidator( return new RecursiveContextualValidator(
$this->contextFactory->createContext($this, $root), $this->contextFactory->createContext($this, $root),
$this->metadataFactory, $this->metadataFactory,
$this->validatorFactory $this->validatorFactory,
$this->objectInitializers
); );
} }
@ -77,7 +86,8 @@ class RecursiveValidator implements ValidatorInterface
return new RecursiveContextualValidator( return new RecursiveContextualValidator(
$context, $context,
$this->metadataFactory, $this->metadataFactory,
$this->validatorFactory $this->validatorFactory,
$this->objectInitializers
); );
} }

View File

@ -12,7 +12,6 @@
namespace Symfony\Component\Validator; namespace Symfony\Component\Validator;
use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\CachedReader; use Doctrine\Common\Annotations\CachedReader;
use Doctrine\Common\Annotations\Reader; use Doctrine\Common\Annotations\Reader;
use Doctrine\Common\Cache\ArrayCache; use Doctrine\Common\Cache\ArrayCache;
@ -367,20 +366,6 @@ class ValidatorBuilder implements ValidatorBuilderInterface
if ($this->annotationReader) { if ($this->annotationReader) {
$loaders[] = new AnnotationLoader($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; $loader = null;
@ -411,9 +396,9 @@ class ValidatorBuilder implements ValidatorBuilderInterface
$contextFactory = new LegacyExecutionContextFactory($metadataFactory, $translator, $this->translationDomain); $contextFactory = new LegacyExecutionContextFactory($metadataFactory, $translator, $this->translationDomain);
if (Validation::API_VERSION_2_5 === $apiVersion) { if (Validation::API_VERSION_2_5 === $apiVersion) {
return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory); return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory, $this->initializers);
} }
return new LegacyValidator($contextFactory, $metadataFactory, $validatorFactory); return new LegacyValidator($contextFactory, $metadataFactory, $validatorFactory, $this->initializers);
} }
} }