Merge branch '2.3' into 2.4

* 2.3:
  [Translator] Use quote to surround invalid locale
  Optimize assertLocale regexp
  [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
This commit is contained in:
Fabien Potencier 2014-07-24 18:59:59 +02:00
commit b07a6b6201
10 changed files with 100 additions and 15 deletions

View File

@ -45,7 +45,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
{
$translator = $this->getTranslator($this->getLoader());
$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('bar (EN)', $translator->trans('bar'));
@ -55,6 +55,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
}
public function testTransWithCaching()
@ -62,7 +63,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
// prime the cache
$translator = $this->getTranslator($this->getLoader(), array('cache_dir' => $this->tmpDir));
$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('bar (EN)', $translator->trans('bar'));
@ -72,12 +73,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$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
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
$translator = $this->getTranslator($loader, array('cache_dir' => $this->tmpDir));
$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('bar (EN)', $translator->trans('bar'));
@ -87,6 +89,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo'));
$this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1));
$this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz'));
$this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax'));
}
public function testGetLocale()
@ -185,6 +188,13 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
'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;
}
@ -216,6 +226,7 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase
$translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese
$translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian Portuguese
$translator->addResource('loader', 'foo', 'fr.UTF-8');
$translator->addResource('loader', 'foo', 'sr@latin'); // Latin Serbian
return $translator;
}

View File

@ -152,6 +152,8 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
* Returns a preconfigured IntlDateFormatter instance
*
* @return \IntlDateFormatter
*
* @throws TransformationFailedException in case the date formatter can not be constructed.
*/
protected function getIntlDateFormatter()
{
@ -162,6 +164,12 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer
$pattern = $this->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);
return $intlDateFormatter;

View File

@ -77,6 +77,12 @@ class DateType extends AbstractType
$calendar,
$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);
if ('choice' === $options['widget']) {

View File

@ -284,6 +284,8 @@ class ProcessPipes
private function readStreams($blocking, $close = false)
{
if (empty($this->pipes)) {
usleep(Process::TIMEOUT_PRECISION * 1E4);
return array();
}

View File

@ -615,7 +615,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
}
$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()

View File

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

View File

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

View File

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

View File

@ -11,6 +11,8 @@
namespace Symfony\Component\Validator\Tests;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\ExecutionContextInterface;
use Symfony\Component\Validator\Tests\Fixtures\FakeMetadataFactory;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Tests\Fixtures\Reference;
@ -561,4 +563,50 @@ class ValidationVisitorTest extends \PHPUnit_Framework_TestCase
$this->visitor->validate($entity, 'Default', '');
}
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->visitor = new ValidationVisitor('Root', $this->metadataFactory, new ConstraintValidatorFactory(), new DefaultTranslator(), null, 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(array($callback)));
$this->visitor->validate($entity, 'Default', '');
$this->assertTrue($entity->initialized);
}
}

View File

@ -127,16 +127,19 @@ class ValidationVisitor implements ValidationVisitorInterface, GlobalExecutionCo
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
// traversing the object graph
$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