From 0b8b58c58661c9e99ecdb6dc107759d1831cfeec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Haso=C5=88?= Date: Fri, 20 Feb 2015 23:49:14 +0100 Subject: [PATCH 01/47] [DependencyInjection] Fixed decoration of service for service with parent --- .../ResolveDefinitionTemplatesPass.php | 8 +++++ .../DefinitionDecorator.php | 10 ++++++ .../ResolveDefinitionTemplatesPassTest.php | 34 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php index f57ba0a4cd..4e80632926 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ResolveDefinitionTemplatesPass.php @@ -119,6 +119,14 @@ class ResolveDefinitionTemplatesPass implements CompilerPassInterface if (isset($changes['lazy'])) { $def->setLazy($definition->isLazy()); } + if (isset($changes['decorated_service'])) { + $decoratedService = $definition->getDecoratedService(); + if (null === $decoratedService) { + $def->setDecoratedService($decoratedService); + } else { + $def->setDecoratedService($decoratedService[0], $decoratedService[1]); + } + } // merge arguments foreach ($definition->getArguments() as $k => $v) { diff --git a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php index d4b45d34ad..f5a1485a5f 100644 --- a/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php +++ b/src/Symfony/Component/DependencyInjection/DefinitionDecorator.php @@ -170,6 +170,16 @@ class DefinitionDecorator extends Definition return parent::setLazy($boolean); } + /** + * {@inheritdoc} + */ + public function setDecoratedService($id, $renamedId = null) + { + $this->changes['decorated_service'] = true; + + return parent::setDecoratedService($id, $renamedId); + } + /** * Gets an argument to pass to the service constructor/factory method. * diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php index fb2bb5fca3..845edd2c14 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php @@ -117,6 +117,25 @@ class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array(), $def->getTags()); } + public function testProcessDoesNotCopyDecoratedService() + { + $container = new ContainerBuilder(); + + $container + ->register('parent') + ->setDecoratedService('foo') + ; + + $container + ->setDefinition('child', new DefinitionDecorator('parent')) + ; + + $this->process($container); + + $def = $container->getDefinition('child'); + $this->assertNull($def->getDecoratedService()); + } + public function testProcessHandlesMultipleInheritance() { $container = new ContainerBuilder(); @@ -173,6 +192,21 @@ class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase $this->assertTrue($container->getDefinition('child1')->isLazy()); } + public function testSetDecoratedServiceOnServiceHasParent() + { + $container = new ContainerBuilder(); + + $container->register('parent', 'stdClass'); + + $container->setDefinition('child1', new DefinitionDecorator('parent')) + ->setDecoratedService('foo', 'foo_inner') + ; + + $this->process($container); + + $this->assertEquals(array('foo', 'foo_inner'), $container->getDefinition('child1')->getDecoratedService()); + } + protected function process(ContainerBuilder $container) { $pass = new ResolveDefinitionTemplatesPass(); From 64910338ea4ca621da6832058ebab05ce44961d9 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Wed, 3 Jun 2015 23:17:06 +0200 Subject: [PATCH 02/47] [Validator] more strict e-mail validation regex --- .../Component/Validator/Constraints/EmailValidator.php | 4 +--- .../Validator/Tests/Constraints/EmailValidatorTest.php | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Validator/Constraints/EmailValidator.php b/src/Symfony/Component/Validator/Constraints/EmailValidator.php index 8d3a7c5104..903ce59711 100644 --- a/src/Symfony/Component/Validator/Constraints/EmailValidator.php +++ b/src/Symfony/Component/Validator/Constraints/EmailValidator.php @@ -24,8 +24,6 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException; class EmailValidator extends ConstraintValidator { /** - * isStrict - * * @var bool */ private $isStrict; @@ -73,7 +71,7 @@ class EmailValidator extends ConstraintValidator return; } - } elseif (!preg_match('/.+\@.+\..+/', $value)) { + } elseif (!preg_match('/^.+\@\S+\.\S+$/', $value)) { $this->buildViolation($constraint->message) ->setParameter('{{ value }}', $this->formatValue($value)) ->setCode(Email::INVALID_FORMAT_ERROR) diff --git a/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php b/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php index 0361333fdc..7e7a5cc70f 100644 --- a/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php +++ b/src/Symfony/Component/Validator/Tests/Constraints/EmailValidatorTest.php @@ -91,6 +91,7 @@ class EmailValidatorTest extends AbstractConstraintValidatorTest array('example'), array('example@'), array('example@localhost'), + array('foo@example.com bar'), ); } From a8f315b8dd419a671baa64545603d6b720c1d983 Mon Sep 17 00:00:00 2001 From: Abdellatif Ait boudad Date: Fri, 19 Jun 2015 15:48:24 +0000 Subject: [PATCH 03/47] [Translation][update cmd] taken account into bundle overrides path. --- .../Command/TranslationUpdateCommand.php | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 6a9b37227b..bdea20fdf7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -61,6 +61,8 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { + $kernel = $this->getContainer()->get('kernel'); + // check presence of force or dump-message if ($input->getOption('force') !== true && $input->getOption('dump-messages') !== true) { $output->writeln('You must choose one of --force or --dump-messages'); @@ -79,8 +81,12 @@ EOF } // get bundle directory - $foundBundle = $this->getApplication()->getKernel()->getBundle($input->getArgument('bundle')); - $bundleTransPath = $foundBundle->getPath().'/Resources/translations'; + $foundBundle = $kernel->getBundle($input->getArgument('bundle')); + $bundleTransPaths = array( + $foundBundle->getPath().'/Resources/', + sprintf('%s/Resources/%s/', $kernel->getRootDir(), $foundBundle->getName()), + ); + $output->writeln(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $foundBundle->getName())); // load any messages from templates @@ -88,13 +94,23 @@ EOF $output->writeln('Parsing templates'); $extractor = $this->getContainer()->get('translation.extractor'); $extractor->setPrefix($input->getOption('prefix')); - $extractor->extract($foundBundle->getPath().'/Resources/views/', $extractedCatalogue); + foreach ($bundleTransPaths as $path) { + $path = $path.'views'; + if (is_dir($path)) { + $extractor->extract($path, $extractedCatalogue); + } + } // load any existing messages from the translation files $currentCatalogue = new MessageCatalogue($input->getArgument('locale')); $output->writeln('Loading translation files'); $loader = $this->getContainer()->get('translation.loader'); - $loader->loadMessages($bundleTransPath, $currentCatalogue); + foreach ($bundleTransPaths as $path) { + $path = $path.'translations'; + if (is_dir($path)) { + $loader->loadMessages($path, $currentCatalogue); + } + } // process catalogues $operation = $input->getOption('clean') @@ -133,7 +149,17 @@ EOF // save the files if ($input->getOption('force') === true) { $output->writeln('Writing files'); - $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath)); + $bundleTransPath = false; + foreach ($bundleTransPaths as $path) { + $path = $path.'translations'; + if (is_dir($path)) { + $bundleTransPath = $path; + } + } + + if ($bundleTransPath) { + $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath)); + } } } } From c3a077a38b8b95891210aa8a2575e4ffad68079f Mon Sep 17 00:00:00 2001 From: Abdellatif Ait boudad Date: Fri, 19 Jun 2015 15:20:37 +0000 Subject: [PATCH 04/47] [Translation][debug cmd] taken account into bundle overrides path. --- .../Command/TranslationDebugCommand.php | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php index 24cc95084c..0f8ab152d9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php @@ -83,17 +83,31 @@ EOF { $locale = $input->getArgument('locale'); $domain = $input->getOption('domain'); - $bundle = $this->getContainer()->get('kernel')->getBundle($input->getArgument('bundle')); + $kernel = $this->getContainer()->get('kernel'); + $bundle = $kernel->getBundle($input->getArgument('bundle')); $loader = $this->getContainer()->get('translation.loader'); // Extract used messages $extractedCatalogue = new MessageCatalogue($locale); - $this->getContainer()->get('translation.extractor')->extract($bundle->getPath().'/Resources/views', $extractedCatalogue); + $bundlePaths = array( + $bundle->getPath().'/Resources/', + sprintf('%s/Resources/%s/', $kernel->getRootDir(), $bundle->getName()), + ); + + foreach ($bundlePaths as $path) { + $path = $path.'views'; + if (is_dir($path)) { + $this->getContainer()->get('translation.extractor')->extract($path, $extractedCatalogue); + } + } // Load defined messages $currentCatalogue = new MessageCatalogue($locale); - if (is_dir($bundle->getPath().'/Resources/translations')) { - $loader->loadMessages($bundle->getPath().'/Resources/translations', $currentCatalogue); + foreach ($bundlePaths as $path) { + $path = $path.'translations'; + if (is_dir($path)) { + $loader->loadMessages($path, $currentCatalogue); + } } // Merge defined and extracted messages to get all message ids From 8602a4b15696a1505f6e23e2ca7ac47ebc252c56 Mon Sep 17 00:00:00 2001 From: Diego Saint Esteben Date: Sun, 21 Jun 2015 15:10:28 -0300 Subject: [PATCH 05/47] Fixed wrong phpdocs --- .../FrameworkBundle/DependencyInjection/FrameworkExtension.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 6b29a8e60b..ac536e8d88 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -431,7 +431,7 @@ class FrameworkExtension extends Extension /** * Loads the request configuration. * - * @param array $config A session configuration array + * @param array $config A request configuration array * @param ContainerBuilder $container A ContainerBuilder instance * @param XmlFileLoader $loader An XmlFileLoader instance */ From aa5e616511f96c2542faede5e4c3dbb7e76ad8b9 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Sun, 21 Jun 2015 07:33:01 +0200 Subject: [PATCH 06/47] [2.3] Static Code Analysis for Components --- src/Symfony/Component/BrowserKit/Cookie.php | 2 +- .../ClassLoader/Tests/ClassMapGeneratorTest.php | 2 +- .../Config/Tests/Resource/DirectoryResourceTest.php | 2 +- .../Component/Config/Tests/Resource/FileResourceTest.php | 2 +- src/Symfony/Component/Console/Input/ArgvInput.php | 2 +- .../Component/Filesystem/Tests/FilesystemTest.php | 2 +- .../Extension/Core/Type/ChoiceTypePerformanceTest.php | 2 +- src/Symfony/Component/HttpFoundation/Request.php | 9 ++++++++- .../Tests/Data/Bundle/Writer/JsonBundleWriterTest.php | 2 +- .../Tests/Data/Bundle/Writer/PhpBundleWriterTest.php | 2 +- .../Tests/Data/Bundle/Writer/TextBundleWriterTest.php | 2 +- .../Component/Intl/Tests/Data/Util/LocaleScannerTest.php | 2 +- .../Templating/Tests/Loader/CacheLoaderTest.php | 2 +- 13 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/Symfony/Component/BrowserKit/Cookie.php b/src/Symfony/Component/BrowserKit/Cookie.php index 424f78bab4..18b9324403 100644 --- a/src/Symfony/Component/BrowserKit/Cookie.php +++ b/src/Symfony/Component/BrowserKit/Cookie.php @@ -77,7 +77,7 @@ class Cookie if (null !== $expires) { $timestampAsDateTime = \DateTime::createFromFormat('U', $expires); if (false === $timestampAsDateTime) { - throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.'), $expires); + throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.', $expires)); } $this->expires = $timestampAsDateTime->getTimestamp(); diff --git a/src/Symfony/Component/ClassLoader/Tests/ClassMapGeneratorTest.php b/src/Symfony/Component/ClassLoader/Tests/ClassMapGeneratorTest.php index 88099258ec..29c3288347 100644 --- a/src/Symfony/Component/ClassLoader/Tests/ClassMapGeneratorTest.php +++ b/src/Symfony/Component/ClassLoader/Tests/ClassMapGeneratorTest.php @@ -22,7 +22,7 @@ class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase public function prepare_workspace() { - $this->workspace = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.time().rand(0, 1000); + $this->workspace = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.time().mt_rand(0, 1000); mkdir($this->workspace, 0777, true); $this->workspace = realpath($this->workspace); } diff --git a/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php index d78e0cf7f6..226e2807dc 100644 --- a/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/DirectoryResourceTest.php @@ -69,7 +69,7 @@ class DirectoryResourceTest extends \PHPUnit_Framework_TestCase $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if the resource has not changed'); $this->assertFalse($resource->isFresh(time() - 86400), '->isFresh() returns false if the resource has been updated'); - $resource = new DirectoryResource('/____foo/foobar'.rand(1, 999999)); + $resource = new DirectoryResource('/____foo/foobar'.mt_rand(1, 999999)); $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the resource does not exist'); } diff --git a/src/Symfony/Component/Config/Tests/Resource/FileResourceTest.php b/src/Symfony/Component/Config/Tests/Resource/FileResourceTest.php index d152806d0c..db85cf7bd0 100644 --- a/src/Symfony/Component/Config/Tests/Resource/FileResourceTest.php +++ b/src/Symfony/Component/Config/Tests/Resource/FileResourceTest.php @@ -48,7 +48,7 @@ class FileResourceTest extends \PHPUnit_Framework_TestCase $this->assertTrue($this->resource->isFresh($this->time + 10), '->isFresh() returns true if the resource has not changed'); $this->assertFalse($this->resource->isFresh($this->time - 86400), '->isFresh() returns false if the resource has been updated'); - $resource = new FileResource('/____foo/foobar'.rand(1, 999999)); + $resource = new FileResource('/____foo/foobar'.mt_rand(1, 999999)); $this->assertFalse($resource->isFresh($this->time), '->isFresh() returns false if the resource does not exist'); } diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 97b0cfc05d..43cbe725fb 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -221,7 +221,7 @@ class ArgvInput extends Input } if (null !== $value && !$option->acceptValue()) { - throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); } if (null === $value && $option->acceptValue() && count($this->parsed)) { diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index 62dfd9ad91..40b05b88bb 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -51,7 +51,7 @@ class FilesystemTest extends \PHPUnit_Framework_TestCase { $this->umask = umask(0); $this->filesystem = new Filesystem(); - $this->workspace = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.time().rand(0, 1000); + $this->workspace = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.time().mt_rand(0, 1000); mkdir($this->workspace, 0777, true); $this->workspace = realpath($this->workspace); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypePerformanceTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypePerformanceTest.php index 0685946fc1..83430d935c 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypePerformanceTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypePerformanceTest.php @@ -30,7 +30,7 @@ class ChoiceTypePerformanceTest extends FormPerformanceTestCase $choices = range(1, 300); for ($i = 0; $i < 100; ++$i) { - $this->factory->create('choice', rand(1, 400), array( + $this->factory->create('choice', mt_rand(1, 400), array( 'choices' => $choices, )); } diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 9e94407ec5..424a2ed09f 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -476,10 +476,17 @@ class Request */ public function __toString() { + $content = ''; + try { + $content = $this->getContent(); + } catch (\LogicException $e) { + trigger_error($e->getMessage(), E_USER_ERROR); + } + return sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". $this->headers."\r\n". - $this->getContent(); + $content; } /** diff --git a/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/JsonBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/JsonBundleWriterTest.php index 69575289cd..c449cf82db 100644 --- a/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/JsonBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/JsonBundleWriterTest.php @@ -39,7 +39,7 @@ class JsonBundleWriterTest extends \PHPUnit_Framework_TestCase } $this->writer = new JsonBundleWriter(); - $this->directory = sys_get_temp_dir().'/JsonBundleWriterTest/'.rand(1000, 9999); + $this->directory = sys_get_temp_dir().'/JsonBundleWriterTest/'.mt_rand(1000, 9999); $this->filesystem = new Filesystem(); $this->filesystem->mkdir($this->directory); diff --git a/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/PhpBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/PhpBundleWriterTest.php index ca3fd71238..a855fbffef 100644 --- a/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/PhpBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/PhpBundleWriterTest.php @@ -35,7 +35,7 @@ class PhpBundleWriterTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->writer = new PhpBundleWriter(); - $this->directory = sys_get_temp_dir().'/PhpBundleWriterTest/'.rand(1000, 9999); + $this->directory = sys_get_temp_dir().'/PhpBundleWriterTest/'.mt_rand(1000, 9999); $this->filesystem = new Filesystem(); $this->filesystem->mkdir($this->directory); diff --git a/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/TextBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/TextBundleWriterTest.php index 1252be82d1..197a4e1fc0 100644 --- a/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/TextBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/Data/Bundle/Writer/TextBundleWriterTest.php @@ -36,7 +36,7 @@ class TextBundleWriterTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->writer = new TextBundleWriter(); - $this->directory = sys_get_temp_dir().'/TextBundleWriterTest/'.rand(1000, 9999); + $this->directory = sys_get_temp_dir().'/TextBundleWriterTest/'.mt_rand(1000, 9999); $this->filesystem = new Filesystem(); $this->filesystem->mkdir($this->directory); diff --git a/src/Symfony/Component/Intl/Tests/Data/Util/LocaleScannerTest.php b/src/Symfony/Component/Intl/Tests/Data/Util/LocaleScannerTest.php index 34d9713481..7c37c80720 100644 --- a/src/Symfony/Component/Intl/Tests/Data/Util/LocaleScannerTest.php +++ b/src/Symfony/Component/Intl/Tests/Data/Util/LocaleScannerTest.php @@ -33,7 +33,7 @@ class LocaleScannerTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->directory = sys_get_temp_dir().'/LocaleScannerTest/'.rand(1000, 9999); + $this->directory = sys_get_temp_dir().'/LocaleScannerTest/'.mt_rand(1000, 9999); $this->filesystem = new Filesystem(); $this->scanner = new LocaleScanner(); diff --git a/src/Symfony/Component/Templating/Tests/Loader/CacheLoaderTest.php b/src/Symfony/Component/Templating/Tests/Loader/CacheLoaderTest.php index 1eabaeb3c8..c82a71f906 100644 --- a/src/Symfony/Component/Templating/Tests/Loader/CacheLoaderTest.php +++ b/src/Symfony/Component/Templating/Tests/Loader/CacheLoaderTest.php @@ -28,7 +28,7 @@ class CacheLoaderTest extends \PHPUnit_Framework_TestCase public function testLoad() { - $dir = sys_get_temp_dir().DIRECTORY_SEPARATOR.rand(111111, 999999); + $dir = sys_get_temp_dir().DIRECTORY_SEPARATOR.mt_rand(111111, 999999); mkdir($dir, 0777, true); $loader = new ProjectTemplateLoader($varLoader = new ProjectTemplateLoaderVar(), $dir); $loader->setDebugger($debugger = new \Symfony\Component\Templating\Tests\Fixtures\ProjectTemplateDebugger()); From f41d1c929fd7f5b045120f8ea457bf0e07d82a5e Mon Sep 17 00:00:00 2001 From: Dawid Nowak Date: Fri, 12 Jun 2015 19:40:31 +0200 Subject: [PATCH 07/47] [bugfix][MonologBridge] WebProcessor: passing to BaseWebProcessor --- .../Bridge/Monolog/Processor/WebProcessor.php | 4 +- .../Tests/Processor/WebProcessorTest.php | 61 ++++++++++++++++--- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php index 375b505a7d..543bc61ed6 100644 --- a/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php +++ b/src/Symfony/Bridge/Monolog/Processor/WebProcessor.php @@ -22,10 +22,10 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; */ class WebProcessor extends BaseWebProcessor { - public function __construct() + public function __construct(array $extraFields = null) { // Pass an empty array as the default null value would access $_SERVER - parent::__construct(array()); + parent::__construct(array(), $extraFields); } public function onKernelRequest(GetResponseEvent $event) diff --git a/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php b/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php index 751e6095e2..95841fc144 100644 --- a/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php +++ b/src/Symfony/Bridge/Monolog/Tests/Processor/WebProcessorTest.php @@ -19,6 +19,42 @@ use Symfony\Component\HttpKernel\HttpKernelInterface; class WebProcessorTest extends \PHPUnit_Framework_TestCase { public function testUsesRequestServerData() + { + list($event, $server) = $this->createRequestEvent(); + + $processor = new WebProcessor(); + $processor->onKernelRequest($event); + $record = $processor($this->getRecord()); + + $this->assertCount(5, $record['extra']); + $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); + $this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']); + $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); + $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); + $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + } + + public function testCanBeConstructedWithExtraFields() + { + if (!$this->isExtraFieldsSupported()) { + $this->markTestSkipped('WebProcessor of the installed Monolog version does not support $extraFields parameter'); + } + + list($event, $server) = $this->createRequestEvent(); + + $processor = new WebProcessor(array('url', 'referrer')); + $processor->onKernelRequest($event); + $record = $processor($this->getRecord()); + + $this->assertCount(2, $record['extra']); + $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); + $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + } + + /** + * @return array + */ + private function createRequestEvent() { $server = array( 'REQUEST_URI' => 'A', @@ -41,15 +77,7 @@ class WebProcessorTest extends \PHPUnit_Framework_TestCase ->method('getRequest') ->will($this->returnValue($request)); - $processor = new WebProcessor(); - $processor->onKernelRequest($event); - $record = $processor($this->getRecord()); - - $this->assertEquals($server['REQUEST_URI'], $record['extra']['url']); - $this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']); - $this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']); - $this->assertEquals($server['SERVER_NAME'], $record['extra']['server']); - $this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']); + return array($event, $server); } /** @@ -58,7 +86,7 @@ class WebProcessorTest extends \PHPUnit_Framework_TestCase * * @return array Record */ - protected function getRecord($level = Logger::WARNING, $message = 'test') + private function getRecord($level = Logger::WARNING, $message = 'test') { return array( 'message' => $message, @@ -70,4 +98,17 @@ class WebProcessorTest extends \PHPUnit_Framework_TestCase 'extra' => array(), ); } + + private function isExtraFieldsSupported() + { + $monologWebProcessorClass = new \ReflectionClass('Monolog\Processor\WebProcessor'); + + foreach ($monologWebProcessorClass->getConstructor()->getParameters() as $parameter) { + if ('extraFields' === $parameter->getName()) { + return true; + } + } + + return false; + } } From 7623dc87e858d2d4b0cee0213386505924eb5f75 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 22 Jun 2015 14:14:00 +0200 Subject: [PATCH 08/47] [Form] Fixed handling of choices passed in choice groups --- .../Form/ChoiceList/ArrayChoiceList.php | 144 +++++++++++----- .../Form/ChoiceList/ArrayKeyChoiceList.php | 56 +++++- .../Form/ChoiceList/ChoiceListInterface.php | 64 +++++-- .../Factory/CachingFactoryDecorator.php | 29 +++- .../Factory/ChoiceListFactoryInterface.php | 27 ++- .../Factory/DefaultChoiceListFactory.php | 163 ++++-------------- .../Factory/PropertyAccessDecorator.php | 12 +- .../Form/ChoiceList/LazyChoiceList.php | 34 +++- .../ChoiceList/LegacyChoiceListAdapter.php | 144 ++++++++++++++++ .../Core/ChoiceList/ChoiceListInterface.php | 52 +++++- .../Form/Extension/Core/Type/ChoiceType.php | 8 +- .../ChoiceList/AbstractChoiceListTest.php | 49 +++++- .../Tests/ChoiceList/ArrayChoiceListTest.php | 24 ++- .../ChoiceList/ArrayKeyChoiceListTest.php | 53 ++---- .../Factory/CachingFactoryDecoratorTest.php | 8 +- .../Factory/DefaultChoiceListFactoryTest.php | 127 +++++++------- .../Tests/ChoiceList/LazyChoiceListTest.php | 30 ++++ .../LegacyChoiceListAdapterTest.php | 110 ++++++++++++ .../FixRadioInputListenerTest.php | 11 +- .../Extension/Core/Type/ChoiceTypeTest.php | 26 +++ 20 files changed, 818 insertions(+), 353 deletions(-) create mode 100644 src/Symfony/Component/Form/ChoiceList/LegacyChoiceListAdapter.php create mode 100644 src/Symfony/Component/Form/Tests/ChoiceList/LegacyChoiceListAdapterTest.php diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index f55154b085..9996966f19 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -30,14 +30,21 @@ class ArrayChoiceList implements ChoiceListInterface * * @var array */ - protected $choices = array(); + protected $choices; /** - * The values of the choices. + * The values indexed by the original keys. * - * @var string[] + * @var array */ - protected $values = array(); + protected $structuredValues; + + /** + * The original keys of the choices array. + * + * @var int[]|string[] + */ + protected $originalKeys; /** * The callback for creating the value for a choice. @@ -51,31 +58,41 @@ class ArrayChoiceList implements ChoiceListInterface * * The given choice array must have the same array keys as the value array. * - * @param array $choices The selectable choices - * @param callable|null $value The callable for creating the value for a - * choice. If `null` is passed, incrementing - * integers are used as values + * @param array|\Traversable $choices The selectable choices + * @param callable|null $value The callable for creating the value + * for a choice. If `null` is passed, + * incrementing integers are used as + * values */ - public function __construct(array $choices, $value = null) + public function __construct($choices, $value = null) { if (null !== $value && !is_callable($value)) { throw new UnexpectedTypeException($value, 'null or callable'); } - $this->choices = $choices; - $this->values = array(); - $this->valueCallback = $value; - - if (null === $value) { - $i = 0; - foreach ($this->choices as $key => $choice) { - $this->values[$key] = (string) $i++; - } - } else { - foreach ($choices as $key => $choice) { - $this->values[$key] = (string) call_user_func($value, $choice); - } + if ($choices instanceof \Traversable) { + $choices = iterator_to_array($choices); } + + if (null !== $value) { + // If a deterministic value generator was passed, use it later + $this->valueCallback = $value; + } else { + // Otherwise simply generate incrementing integers as values + $i = 0; + $value = function () use (&$i) { + return $i++; + }; + } + + // If the choices are given as recursive array (i.e. with explicit + // choice groups), flatten the array. The grouping information is needed + // in the view only. + $this->flatten($choices, $value, $choicesByValues, $keysByValues, $structuredValues); + + $this->choices = $choicesByValues; + $this->originalKeys = $keysByValues; + $this->structuredValues = $structuredValues; } /** @@ -91,7 +108,23 @@ class ArrayChoiceList implements ChoiceListInterface */ public function getValues() { - return $this->values; + return array_map('strval', array_keys($this->choices)); + } + + /** + * {@inheritdoc} + */ + public function getStructuredValues() + { + return $this->structuredValues; + } + + /** + * {@inheritdoc} + */ + public function getOriginalKeys() + { + return $this->originalKeys; } /** @@ -102,17 +135,8 @@ class ArrayChoiceList implements ChoiceListInterface $choices = array(); foreach ($values as $i => $givenValue) { - foreach ($this->values as $j => $value) { - if ($value !== (string) $givenValue) { - continue; - } - - $choices[$i] = $this->choices[$j]; - unset($values[$i]); - - if (0 === count($values)) { - break 2; - } + if (isset($this->choices[$givenValue])) { + $choices[$i] = $this->choices[$givenValue]; } } @@ -131,28 +155,56 @@ class ArrayChoiceList implements ChoiceListInterface $givenValues = array(); foreach ($choices as $i => $givenChoice) { - $givenValues[$i] = (string) call_user_func($this->valueCallback, $givenChoice); + $givenValues[$i] = call_user_func($this->valueCallback, $givenChoice); } - return array_intersect($givenValues, $this->values); + return array_intersect($givenValues, array_keys($this->choices)); } // Otherwise compare choices by identity foreach ($choices as $i => $givenChoice) { - foreach ($this->choices as $j => $choice) { - if ($choice !== $givenChoice) { - continue; - } - - $values[$i] = $this->values[$j]; - unset($choices[$i]); - - if (0 === count($choices)) { - break 2; + foreach ($this->choices as $value => $choice) { + if ($choice === $givenChoice) { + $values[$i] = (string) $value; + break; } } } return $values; } + + /** + * Flattens an array into the given output variables. + * + * @param array $choices The array to flatten + * @param callable $value The callable for generating choice values + * @param array $choicesByValues The flattened choices indexed by the + * corresponding values + * @param array $keysByValues The original keys indexed by the + * corresponding values + * + * @internal Must not be used by user-land code + */ + protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues) + { + if (null === $choicesByValues) { + $choicesByValues = array(); + $keysByValues = array(); + $structuredValues = array(); + } + + foreach ($choices as $key => $choice) { + if (is_array($choice)) { + $this->flatten($choice, $value, $choicesByValues, $keysByValues, $structuredValues[$key]); + + continue; + } + + $choiceValue = (string) call_user_func($value, $choice); + $choicesByValues[$choiceValue] = $choice; + $keysByValues[$choiceValue] = $key; + $structuredValues[$key] = $choiceValue; + } + } } diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php index 30709108e8..7c3c107d0f 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayKeyChoiceList.php @@ -62,6 +62,8 @@ class ArrayKeyChoiceList extends ArrayChoiceList * @return int|string The choice as PHP array key * * @throws InvalidArgumentException If the choice is not scalar + * + * @internal Must not be used outside this class */ public static function toArrayKey($choice) { @@ -89,23 +91,27 @@ class ArrayKeyChoiceList extends ArrayChoiceList * If no values are given, the choices are cast to strings and used as * values. * - * @param array $choices The selectable choices - * @param callable $value The callable for creating the value for a - * choice. If `null` is passed, the choices are - * cast to strings and used as values + * @param array|\Traversable $choices The selectable choices + * @param callable $value The callable for creating the value + * for a choice. If `null` is passed, the + * choices are cast to strings and used + * as values * * @throws InvalidArgumentException If the keys of the choices don't match * the keys of the values or if any of the * choices is not scalar */ - public function __construct(array $choices, $value = null) + public function __construct($choices, $value = null) { - $choices = array_map(array(__CLASS__, 'toArrayKey'), $choices); - + // If no values are given, use the choices as values + // Since the choices are stored in the collection keys, i.e. they are + // strings or integers, we are guaranteed to be able to convert them + // to strings if (null === $value) { $value = function ($choice) { return (string) $choice; }; + $this->useChoicesAsValues = true; } @@ -122,7 +128,7 @@ class ArrayKeyChoiceList extends ArrayChoiceList // If the values are identical to the choices, so we can just return // them to improve performance a little bit - return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, $this->values)); + return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, array_keys($this->choices))); } return parent::getChoicesForValues($values); @@ -143,4 +149,38 @@ class ArrayKeyChoiceList extends ArrayChoiceList return parent::getValuesForChoices($choices); } + + /** + * Flattens and flips an array into the given output variable. + * + * @param array $choices The array to flatten + * @param callable $value The callable for generating choice values + * @param array $choicesByValues The flattened choices indexed by the + * corresponding values + * @param array $keysByValues The original keys indexed by the + * corresponding values + * + * @internal Must not be used by user-land code + */ + protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues) + { + if (null === $choicesByValues) { + $choicesByValues = array(); + $keysByValues = array(); + $structuredValues = array(); + } + + foreach ($choices as $choice => $key) { + if (is_array($key)) { + $this->flatten($key, $value, $choicesByValues, $keysByValues, $structuredValues[$choice]); + + continue; + } + + $choiceValue = (string) call_user_func($value, $choice); + $choicesByValues[$choiceValue] = $choice; + $keysByValues[$choiceValue] = $key; + $structuredValues[$key] = $choiceValue; + } + } } diff --git a/src/Symfony/Component/Form/ChoiceList/ChoiceListInterface.php b/src/Symfony/Component/Form/ChoiceList/ChoiceListInterface.php index 62f3158646..b59e77bf79 100644 --- a/src/Symfony/Component/Form/ChoiceList/ChoiceListInterface.php +++ b/src/Symfony/Component/Form/ChoiceList/ChoiceListInterface.php @@ -14,16 +14,13 @@ namespace Symfony\Component\Form\ChoiceList; /** * A list of choices that can be selected in a choice field. * - * A choice list assigns string values to each of a list of choices. These - * string values are displayed in the "value" attributes in HTML and submitted - * back to the server. + * A choice list assigns unique string values to each of a list of choices. + * These string values are displayed in the "value" attributes in HTML and + * submitted back to the server. * * The acceptable data types for the choices depend on the implementation. * Values must always be strings and (within the list) free of duplicates. * - * The choices returned by {@link getChoices()} and the values returned by - * {@link getValues()} must have the same array indices. - * * @author Bernhard Schussek */ interface ChoiceListInterface @@ -31,23 +28,66 @@ interface ChoiceListInterface /** * Returns all selectable choices. * - * The keys of the choices correspond to the keys of the values returned by - * {@link getValues()}. - * - * @return array The selectable choices + * @return array The selectable choices indexed by the corresponding values */ public function getChoices(); /** * Returns the values for the choices. * - * The keys of the values correspond to the keys of the choices returned by - * {@link getChoices()}. + * The values are strings that do not contain duplicates. * * @return string[] The choice values */ public function getValues(); + /** + * Returns the values in the structure originally passed to the list. + * + * Contrary to {@link getValues()}, the result is indexed by the original + * keys of the choices. If the original array contained nested arrays, these + * nested arrays are represented here as well: + * + * $form->add('field', 'choice', array( + * 'choices' => array( + * 'Decided' => array('Yes' => true, 'No' => false), + * 'Undecided' => array('Maybe' => null), + * ), + * )); + * + * In this example, the result of this method is: + * + * array( + * 'Decided' => array('Yes' => '0', 'No' => '1'), + * 'Undecided' => array('Maybe' => '2'), + * ) + * + * @return string[] The choice values + */ + public function getStructuredValues(); + + /** + * Returns the original keys of the choices. + * + * The original keys are the keys of the choice array that was passed in the + * "choice" option of the choice type. Note that this array may contain + * duplicates if the "choice" option contained choice groups: + * + * $form->add('field', 'choice', array( + * 'choices' => array( + * 'Decided' => array(true, false), + * 'Undecided' => array(null), + * ), + * )); + * + * In this example, the original key 0 appears twice, once for `true` and + * once for `null`. + * + * @return int[]|string[] The original choice keys indexed by the + * corresponding choice values + */ + public function getOriginalKeys(); + /** * Returns the choices corresponding to the given values. * diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php index f9848c2d0e..8796acfc4d 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php @@ -65,6 +65,30 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface return hash('sha256', $namespace.':'.json_encode($value)); } + /** + * Flattens an array into the given output variable. + * + * @param array $array The array to flatten + * @param array $output The flattened output + * + * @internal Should not be used by user-land code + */ + private static function flatten(array $array, &$output) + { + if (null === $output) { + $output = array(); + } + + foreach ($array as $key => $value) { + if (is_array($value)) { + self::flatten($value, $output); + continue; + } + + $output[$key] = $value; + } + } + /** * Decorates the given factory. * @@ -100,7 +124,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface // We ignore the choice groups for caching. If two choice lists are // requested with the same choices, but a different grouping, the same // choice list is returned. - DefaultChoiceListFactory::flatten($choices, $flatChoices); + self::flatten($choices, $flatChoices); $hash = self::generateHash(array($flatChoices, $value), 'fromChoices'); @@ -129,7 +153,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface // We ignore the choice groups for caching. If two choice lists are // requested with the same choices, but a different grouping, the same // choice list is returned. - DefaultChoiceListFactory::flattenFlipped($choices, $flatChoices); + self::flatten($choices, $flatChoices); $hash = self::generateHash(array($flatChoices, $value), 'fromFlippedChoices'); @@ -161,7 +185,6 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface { // The input is not validated on purpose. This way, the decorated // factory may decide which input to accept and which not. - $hash = self::generateHash(array($list, $preferredChoices, $label, $index, $groupBy, $attr)); if (!isset($this->views[$hash])) { diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php index 60239423f3..7933dd91d4 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/ChoiceListFactoryInterface.php @@ -69,7 +69,7 @@ interface ChoiceListFactoryInterface * argument. * * @param ChoiceLoaderInterface $loader The choice loader - * @param null|callable $value The callable generating the choice + * @param null|callable $value The callable generating the choice * values * * @return ChoiceListInterface The choice list @@ -98,25 +98,20 @@ interface ChoiceListFactoryInterface * The preferred choices can also be passed as array. Each choice that is * contained in that array will be marked as preferred. * - * The groups can be passed as a multi-dimensional array. In that case, a - * group will be created for each array entry containing a nested array. - * For all other entries, the choice for the corresponding key will be - * inserted at that position. - * * The attributes can be passed as multi-dimensional array. The keys should * match the keys of the choices. The values should be arrays of HTML * attributes that should be added to the respective choice. * - * @param ChoiceListInterface $list The choice list - * @param null|array|callable $preferredChoices The preferred choices - * @param null|callable $label The callable generating - * the choice labels - * @param null|callable $index The callable generating - * the view indices - * @param null|array|\Traversable|callable $groupBy The callable generating - * the group names - * @param null|array|callable $attr The callable generating - * the HTML attributes + * @param ChoiceListInterface $list The choice list + * @param null|array|callable $preferredChoices The preferred choices + * @param null|callable $label The callable generating the + * choice labels + * @param null|callable $index The callable generating the + * view indices + * @param null|callable $groupBy The callable generating the + * group names + * @param null|array|callable $attr The callable generating the + * HTML attributes * * @return ChoiceListView The choice list view */ diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index b7f37137d8..cf88c6f581 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -15,11 +15,11 @@ use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList; use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\LazyChoiceList; +use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceListView; use Symfony\Component\Form\ChoiceList\View\ChoiceView; -use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface; use Symfony\Component\Form\Extension\Core\View\ChoiceView as LegacyChoiceView; /** @@ -29,72 +29,12 @@ use Symfony\Component\Form\Extension\Core\View\ChoiceView as LegacyChoiceView; */ class DefaultChoiceListFactory implements ChoiceListFactoryInterface { - /** - * Flattens an array into the given output variable. - * - * @param array $array The array to flatten - * @param array $output The flattened output - * - * @internal Should not be used by user-land code - */ - public static function flatten(array $array, &$output) - { - if (null === $output) { - $output = array(); - } - - foreach ($array as $key => $value) { - if (is_array($value)) { - self::flatten($value, $output); - continue; - } - - $output[$key] = $value; - } - } - - /** - * Flattens and flips an array into the given output variable. - * - * During the flattening, the keys and values of the input array are - * flipped. - * - * @param array $array The array to flatten - * @param array $output The flattened output - * - * @internal Should not be used by user-land code - */ - public static function flattenFlipped(array $array, &$output) - { - if (null === $output) { - $output = array(); - } - - foreach ($array as $key => $value) { - if (is_array($value)) { - self::flattenFlipped($value, $output); - continue; - } - - $output[$value] = $key; - } - } - /** * {@inheritdoc} */ public function createListFromChoices($choices, $value = null) { - if ($choices instanceof \Traversable) { - $choices = iterator_to_array($choices); - } - - // If the choices are given as recursive array (i.e. with explicit - // choice groups), flatten the array. The grouping information is needed - // in the view only. - self::flatten($choices, $flatChoices); - - return new ArrayChoiceList($flatChoices, $value); + return new ArrayChoiceList($choices, $value); } /** @@ -105,26 +45,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface */ public function createListFromFlippedChoices($choices, $value = null) { - if ($choices instanceof \Traversable) { - $choices = iterator_to_array($choices); - } - - // If the choices are given as recursive array (i.e. with explicit - // choice groups), flatten the array. The grouping information is needed - // in the view only. - self::flattenFlipped($choices, $flatChoices); - - // If no values are given, use the choices as values - // Since the choices are stored in the collection keys, i.e. they are - // strings or integers, we are guaranteed to be able to convert them - // to strings - if (null === $value) { - $value = function ($choice) { - return (string) $choice; - }; - } - - return new ArrayKeyChoiceList($flatChoices, $value); + return new ArrayKeyChoiceList($choices, $value); } /** @@ -141,22 +62,24 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null) { // Backwards compatibility - if ($list instanceof LegacyChoiceListInterface && empty($preferredChoices) + if ($list instanceof LegacyChoiceListAdapter && empty($preferredChoices) && null === $label && null === $index && null === $groupBy && null === $attr) { $mapToNonLegacyChoiceView = function (LegacyChoiceView $choiceView) { return new ChoiceView($choiceView->data, $choiceView->value, $choiceView->label); }; + $adaptedList = $list->getAdaptedList(); + return new ChoiceListView( - array_map($mapToNonLegacyChoiceView, $list->getRemainingViews()), - array_map($mapToNonLegacyChoiceView, $list->getPreferredViews()) + array_map($mapToNonLegacyChoiceView, $adaptedList->getRemainingViews()), + array_map($mapToNonLegacyChoiceView, $adaptedList->getPreferredViews()) ); } $preferredViews = array(); $otherViews = array(); $choices = $list->getChoices(); - $values = $list->getValues(); + $keys = $list->getOriginalKeys(); if (!is_callable($preferredChoices) && !empty($preferredChoices)) { $preferredChoices = function ($choice) use ($preferredChoices) { @@ -169,36 +92,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface $index = 0; } - // If $groupBy is not given, no grouping is done - if (empty($groupBy)) { - foreach ($choices as $key => $choice) { - self::addChoiceView( - $choice, - $key, - $label, - $values, - $index, - $attr, - $preferredChoices, - $preferredViews, - $otherViews - ); - } - - return new ChoiceListView($otherViews, $preferredViews); - } - // If $groupBy is a callable, choices are added to the group with the // name returned by the callable. If the callable returns null, the // choice is not added to any group if (is_callable($groupBy)) { - foreach ($choices as $key => $choice) { + foreach ($choices as $value => $choice) { self::addChoiceViewGroupedBy( $groupBy, $choice, - $key, + (string) $value, $label, - $values, + $keys, $index, $attr, $preferredChoices, @@ -207,13 +111,12 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface ); } } else { - // If $groupBy is passed as array, use that array as template for - // constructing the groups + // Otherwise use the original structure of the choices self::addChoiceViewsGroupedBy( - $groupBy, + $list->getStructuredValues(), $label, $choices, - $values, + $keys, $index, $attr, $preferredChoices, @@ -239,15 +142,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface return new ChoiceListView($otherViews, $preferredViews); } - private static function addChoiceView($choice, $key, $label, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) + private static function addChoiceView($choice, $value, $label, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) { - $value = $values[$key]; + // $value may be an integer or a string, since it's stored in the array + // keys. We want to guarantee it's a string though. + $key = $keys[$value]; $nextIndex = is_int($index) ? $index++ : call_user_func($index, $choice, $key, $value); $view = new ChoiceView( $choice, $value, - // If the labels are null, use the choice key by default + // If the labels are null, use the original choice key by default null === $label ? (string) $key : (string) call_user_func($label, $choice, $key, $value), // The attributes may be a callable or a mapping from choice indices // to nested arrays @@ -262,19 +167,19 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface } } - private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) + private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) { - foreach ($groupBy as $key => $content) { + foreach ($groupBy as $key => $value) { // Add the contents of groups to new ChoiceGroupView instances - if (is_array($content)) { + if (is_array($value)) { $preferredViewsForGroup = array(); $otherViewsForGroup = array(); self::addChoiceViewsGroupedBy( - $content, + $value, $label, $choices, - $values, + $keys, $index, $attr, $isPreferred, @@ -295,10 +200,10 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface // Add ungrouped items directly self::addChoiceView( - $choices[$key], - $key, + $choices[$value], + $value, $label, - $values, + $keys, $index, $attr, $isPreferred, @@ -308,17 +213,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface } } - private static function addChoiceViewGroupedBy($groupBy, $choice, $key, $label, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) + private static function addChoiceViewGroupedBy($groupBy, $choice, $value, $label, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) { - $groupLabel = call_user_func($groupBy, $choice, $key, $values[$key]); + $groupLabel = call_user_func($groupBy, $choice, $keys[$value], $value); if (null === $groupLabel) { // If the callable returns null, don't group the choice self::addChoiceView( $choice, - $key, + $value, $label, - $values, + $keys, $index, $attr, $isPreferred, @@ -338,9 +243,9 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface self::addChoiceView( $choice, - $key, + $value, $label, - $values, + $keys, $index, $attr, $isPreferred, diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php index 0f4bcaa14e..e86772fddc 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/PropertyAccessDecorator.php @@ -54,8 +54,8 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface /** * Decorates the given factory. * - * @param ChoiceListFactoryInterface $decoratedFactory The decorated factory - * @param null|PropertyAccessorInterface $propertyAccessor The used property accessor + * @param ChoiceListFactoryInterface $decoratedFactory The decorated factory + * @param null|PropertyAccessorInterface $propertyAccessor The used property accessor */ public function __construct(ChoiceListFactoryInterface $decoratedFactory, PropertyAccessorInterface $propertyAccessor = null) { @@ -98,8 +98,6 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface if (is_object($choice) || is_array($choice)) { return $accessor->getValue($choice, $value); } - - return; }; } @@ -128,9 +126,9 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface /** * {@inheritdoc} * - * @param ChoiceLoaderInterface $loader The choice loader - * @param null|callable|string|PropertyPath $value The callable or path for - * generating the choice values + * @param ChoiceLoaderInterface $loader The choice loader + * @param null|callable|string|PropertyPath $value The callable or path for + * generating the choice values * * @return ChoiceListInterface The choice list */ diff --git a/src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php b/src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php index 092e2c4644..f691d71330 100644 --- a/src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/LazyChoiceList.php @@ -43,13 +43,6 @@ class LazyChoiceList implements ChoiceListInterface */ private $value; - /** - * Whether to use the value callback to compare choices. - * - * @var bool - */ - private $compareByValue; - /** * @var ChoiceListInterface|null */ @@ -66,11 +59,10 @@ class LazyChoiceList implements ChoiceListInterface * @param null|callable $value The callable generating the choice * values */ - public function __construct(ChoiceLoaderInterface $loader, $value = null, $compareByValue = false) + public function __construct(ChoiceLoaderInterface $loader, $value = null) { $this->loader = $loader; $this->value = $value; - $this->compareByValue = $compareByValue; } /** @@ -97,6 +89,30 @@ class LazyChoiceList implements ChoiceListInterface return $this->loadedList->getValues(); } + /** + * {@inheritdoc} + */ + public function getStructuredValues() + { + if (!$this->loadedList) { + $this->loadedList = $this->loader->loadChoiceList($this->value); + } + + return $this->loadedList->getStructuredValues(); + } + + /** + * {@inheritdoc} + */ + public function getOriginalKeys() + { + if (!$this->loadedList) { + $this->loadedList = $this->loader->loadChoiceList($this->value); + } + + return $this->loadedList->getOriginalKeys(); + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Form/ChoiceList/LegacyChoiceListAdapter.php b/src/Symfony/Component/Form/ChoiceList/LegacyChoiceListAdapter.php new file mode 100644 index 0000000000..929ef8c290 --- /dev/null +++ b/src/Symfony/Component/Form/ChoiceList/LegacyChoiceListAdapter.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList; + +use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface; + +/** + * Adapts a legacy choice list implementation to {@link ChoiceListInterface}. + * + * @author Bernhard Schussek + * + * @deprecated Added for backwards compatibility in Symfony 2.7, to be + * removed in Symfony 3.0. + */ +class LegacyChoiceListAdapter implements ChoiceListInterface +{ + /** + * @var LegacyChoiceListInterface + */ + private $adaptedList; + + /** + * @var array|null + */ + private $choices; + + /** + * @var array|null + */ + private $values; + + /** + * @var array|null + */ + private $structuredValues; + + /** + * Adapts a legacy choice list to {@link ChoiceListInterface}. + * + * @param LegacyChoiceListInterface $adaptedList The adapted list + */ + public function __construct(LegacyChoiceListInterface $adaptedList) + { + $this->adaptedList = $adaptedList; + } + + /** + * {@inheritdoc} + */ + public function getChoices() + { + if (!$this->choices) { + $this->initialize(); + } + + return $this->choices; + } + + /** + * {@inheritdoc} + */ + public function getValues() + { + if (!$this->values) { + $this->initialize(); + } + + return $this->values; + } + + /** + * {@inheritdoc} + */ + public function getStructuredValues() + { + if (!$this->structuredValues) { + $this->initialize(); + } + + return $this->structuredValues; + } + + /** + * {@inheritdoc} + */ + public function getOriginalKeys() + { + if (!$this->structuredValues) { + $this->initialize(); + } + + return array_flip($this->structuredValues); + } + + /** + * {@inheritdoc} + */ + public function getChoicesForValues(array $values) + { + return $this->adaptedList->getChoicesForValues($values); + } + + /** + * {@inheritdoc} + */ + public function getValuesForChoices(array $choices) + { + return $this->adaptedList->getValuesForChoices($choices); + } + + /** + * Returns the adapted choice list. + * + * @return LegacyChoiceListInterface The adapted list + */ + public function getAdaptedList() + { + return $this->adaptedList; + } + + private function initialize() + { + $this->choices = array(); + $this->values = array(); + $this->structuredValues = $this->adaptedList->getValues(); + + $innerChoices = $this->adaptedList->getChoices(); + + foreach ($innerChoices as $index => $choice) { + $value = $this->structuredValues[$index]; + $this->values[] = $value; + $this->choices[$value] = $choice; + } + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php index ac5847ed32..f7f8acdfea 100644 --- a/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php +++ b/src/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php @@ -11,9 +11,6 @@ namespace Symfony\Component\Form\Extension\Core\ChoiceList; -use Symfony\Component\Form\ChoiceList\ChoiceListInterface as BaseChoiceListInterface; -use Symfony\Component\Form\FormConfigBuilder; - /** * Contains choices that can be selected in a form field. * @@ -30,10 +27,24 @@ use Symfony\Component\Form\FormConfigBuilder; * @author Bernhard Schussek * * @deprecated since version 2.7, to be removed in 3.0. - * Use {@link BaseChoiceListInterface} instead. + * Use {@link \Symfony\Component\Form\ChoiceList\ChoiceListInterface} instead. */ -interface ChoiceListInterface extends BaseChoiceListInterface +interface ChoiceListInterface { + /** + * Returns the list of choices. + * + * @return array The choices with their indices as keys + */ + public function getChoices(); + + /** + * Returns the values for the choices. + * + * @return array The values with the corresponding choice indices as keys + */ + public function getValues(); + /** * Returns the choice views of the preferred choices as nested array with * the choice groups as top-level keys. @@ -84,6 +95,37 @@ interface ChoiceListInterface extends BaseChoiceListInterface */ public function getRemainingViews(); + /** + * Returns the choices corresponding to the given values. + * + * The choices can have any data type. + * + * The choices must be returned with the same keys and in the same order + * as the corresponding values in the given array. + * + * @param array $values An array of choice values. Not existing values in + * this array are ignored + * + * @return array An array of choices with ascending, 0-based numeric keys + */ + public function getChoicesForValues(array $values); + + /** + * Returns the values corresponding to the given choices. + * + * The values must be strings. + * + * The values must be returned with the same keys and in the same order + * as the corresponding choices in the given array. + * + * @param array $choices An array of choices. Not existing choices in this + * array are ignored + * + * @return array An array of choice values with ascending, 0-based numeric + * keys + */ + public function getValuesForChoices(array $choices); + /** * Returns the indices corresponding to the given choices. * diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index d9791b4739..386f27dbc9 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator; +use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; @@ -27,6 +28,7 @@ use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; +use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface; use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer; @@ -259,6 +261,10 @@ class ChoiceType extends AbstractType if ($choiceList) { @trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED); + if ($choiceList instanceof LegacyChoiceListInterface) { + return new LegacyChoiceListAdapter($choiceList); + } + return $choiceList; } @@ -338,7 +344,7 @@ class ChoiceType extends AbstractType $resolver->setNormalizer('placeholder', $placeholderNormalizer); $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); - $resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\ChoiceList\ChoiceListInterface')); + $resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\ChoiceList\ChoiceListInterface', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface')); $resolver->setAllowedTypes('choices', array('null', 'array', '\Traversable')); $resolver->setAllowedTypes('choice_translation_domain', array('null', 'bool', 'string')); $resolver->setAllowedTypes('choices_as_values', 'bool'); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/AbstractChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/AbstractChoiceListTest.php index 0805238f7f..ca244ebd69 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/AbstractChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/AbstractChoiceListTest.php @@ -31,6 +31,16 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase */ protected $values; + /** + * @var array + */ + protected $structuredValues; + + /** + * @var array + */ + protected $keys; + /** * @var mixed */ @@ -71,25 +81,52 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase */ protected $value4; + /** + * @var string + */ + protected $key1; + + /** + * @var string + */ + protected $key2; + + /** + * @var string + */ + protected $key3; + + /** + * @var string + */ + protected $key4; + protected function setUp() { parent::setUp(); $this->list = $this->createChoiceList(); - $this->choices = $this->getChoices(); + $choices = $this->getChoices(); + $this->values = $this->getValues(); + $this->structuredValues = array_combine(array_keys($choices), $this->values); + $this->choices = array_combine($this->values, $choices); + $this->keys = array_combine($this->values, array_keys($choices)); // allow access to the individual entries without relying on their indices reset($this->choices); reset($this->values); + reset($this->keys); for ($i = 1; $i <= 4; ++$i) { $this->{'choice'.$i} = current($this->choices); $this->{'value'.$i} = current($this->values); + $this->{'key'.$i} = current($this->keys); next($this->choices); next($this->values); + next($this->keys); } } @@ -103,6 +140,16 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase $this->assertSame($this->values, $this->list->getValues()); } + public function testGetStructuredValues() + { + $this->assertSame($this->values, $this->list->getStructuredValues()); + } + + public function testGetOriginalKeys() + { + $this->assertSame($this->keys, $this->list->getOriginalKeys()); + } + public function testGetChoicesForValues() { $values = array($this->value1, $this->value2); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php index 129a093b89..50d4df8a9b 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayChoiceListTest.php @@ -29,8 +29,6 @@ class ArrayChoiceListTest extends AbstractChoiceListTest protected function createChoiceList() { - $i = 0; - return new ArrayChoiceList($this->getChoices()); } @@ -60,11 +58,31 @@ class ArrayChoiceListTest extends AbstractChoiceListTest $choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback); - $this->assertSame(array(2 => ':foo', 7 => ':bar', 10 => ':baz'), $choiceList->getValues()); + $this->assertSame(array(':foo', ':bar', ':baz'), $choiceList->getValues()); + $this->assertSame(array(':foo' => 'foo', ':bar' => 'bar', ':baz' => 'baz'), $choiceList->getChoices()); + $this->assertSame(array(':foo' => 2, ':bar' => 7, ':baz' => 10), $choiceList->getOriginalKeys()); $this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz'))); $this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz'))); } + public function testCreateChoiceListWithGroupedChoices() + { + $choiceList = new ArrayChoiceList(array( + 'Group 1' => array('A' => 'a', 'B' => 'b'), + 'Group 2' => array('C' => 'c', 'D' => 'd'), + )); + + $this->assertSame(array('0', '1', '2', '3'), $choiceList->getValues()); + $this->assertSame(array( + 'Group 1' => array('A' => '0', 'B' => '1'), + 'Group 2' => array('C' => '2', 'D' => '3'), + ), $choiceList->getStructuredValues()); + $this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $choiceList->getChoices()); + $this->assertSame(array(0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D'), $choiceList->getOriginalKeys()); + $this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getChoicesForValues(array(1 => '0', 2 => '1'))); + $this->assertSame(array(1 => '0', 2 => '1'), $choiceList->getValuesForChoices(array(1 => 'a', 2 => 'b'))); + } + public function testCompareChoicesByIdentityByDefault() { $callback = function ($choice) { diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayKeyChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayKeyChoiceListTest.php index 5024a60db7..5cbadf6e0f 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/ArrayKeyChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/ArrayKeyChoiceListTest.php @@ -29,7 +29,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest protected function createChoiceList() { - return new ArrayKeyChoiceList($this->getChoices()); + return new ArrayKeyChoiceList(array_flip($this->getChoices())); } protected function getChoices() @@ -44,9 +44,11 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest public function testUseChoicesAsValuesByDefault() { - $list = new ArrayKeyChoiceList(array(1 => '', 3 => 0, 7 => '1', 10 => 1.23)); + $list = new ArrayKeyChoiceList(array('' => 'Empty', 0 => 'Zero', 1 => 'One', '1.23' => 'Float')); - $this->assertSame(array(1 => '', 3 => '0', 7 => '1', 10 => '1.23'), $list->getValues()); + $this->assertSame(array('', '0', '1', '1.23'), $list->getValues()); + $this->assertSame(array('' => '', 0 => 0, 1 => 1, '1.23' => '1.23'), $list->getChoices()); + $this->assertSame(array('' => 'Empty', 0 => 'Zero', 1 => 'One', '1.23' => 'Float'), $list->getOriginalKeys()); } public function testNoChoices() @@ -102,33 +104,22 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest public function provideConvertibleChoices() { return array( - array(array(0), array(0)), - array(array(1), array(1)), - array(array('0'), array(0)), - array(array('1'), array(1)), - array(array('1.23'), array('1.23')), - array(array('foobar'), array('foobar')), + array(array(0 => 'Label'), array(0 => 0)), + array(array(1 => 'Label'), array(1 => 1)), + array(array('1.23' => 'Label'), array('1.23' => '1.23')), + array(array('foobar' => 'Label'), array('foobar' => 'foobar')), // The default value of choice fields is NULL. It should be treated // like the empty value for this choice list type - array(array(null), array('')), - array(array(1.23), array('1.23')), + array(array(null => 'Label'), array('' => '')), + array(array('1.23' => 'Label'), array('1.23' => '1.23')), // Always cast booleans to 0 and 1, because: // array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No') // see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean - array(array(true), array(1)), - array(array(false), array(0)), + array(array(true => 'Label'), array(1 => 1)), + array(array(false => 'Label'), array(0 => 0)), ); } - /** - * @dataProvider provideInvalidChoices - * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException - */ - public function testFailIfInvalidChoices(array $choices) - { - new ArrayKeyChoiceList($choices); - } - /** * @dataProvider provideInvalidChoices * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException @@ -155,7 +146,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest return $value; }; - $list = new ArrayKeyChoiceList(array('choice'), $callback); + $list = new ArrayKeyChoiceList(array('choice' => 'Label'), $callback); $this->assertSame(array($converted), $list->getValues()); } @@ -169,15 +160,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest array('1', '1'), array('1.23', '1.23'), array('foobar', 'foobar'), - // The default value of choice fields is NULL. It should be treated - // like the empty value for this choice list type - array(null, ''), - array(1.23, '1.23'), - // Always cast booleans to 0 and 1, because: - // array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No') - // see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean - array(true, '1'), - array(false, ''), + array('', ''), ); } @@ -187,9 +170,11 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest return ':'.$choice; }; - $choiceList = new ArrayKeyChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback); + $choiceList = new ArrayKeyChoiceList(array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'), $callback); - $this->assertSame(array(2 => ':foo', 7 => ':bar', 10 => ':baz'), $choiceList->getValues()); + $this->assertSame(array(':foo', ':bar', ':baz'), $choiceList->getValues()); + $this->assertSame(array(':foo' => 'foo', ':bar' => 'bar', ':baz' => 'baz'), $choiceList->getChoices()); + $this->assertSame(array(':foo' => 'Foo', ':bar' => 'Bar', ':baz' => 'Baz'), $choiceList->getOriginalKeys()); $this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz'))); $this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz'))); } diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php index 716468276a..9855c4a708 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/CachingFactoryDecoratorTest.php @@ -204,8 +204,8 @@ class CachingFactoryDecoratorTest extends \PHPUnit_Framework_TestCase */ public function testCreateFromFlippedChoicesSameChoices($choice1, $choice2) { - $choices1 = array($choice1); - $choices2 = array($choice2); + $choices1 = array($choice1 => 'A'); + $choices2 = array($choice2 => 'A'); $list = new \stdClass(); $this->decoratedFactory->expects($this->once()) @@ -222,8 +222,8 @@ class CachingFactoryDecoratorTest extends \PHPUnit_Framework_TestCase */ public function testCreateFromFlippedChoicesDifferentChoices($choice1, $choice2) { - $choices1 = array($choice1); - $choices2 = array($choice2); + $choices1 = array($choice1 => 'A'); + $choices2 = array($choice2 => 'A'); $list1 = new \stdClass(); $list2 = new \stdClass(); diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index e5112bf38f..16a116719c 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; use Symfony\Component\Form\ChoiceList\LazyChoiceList; +use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceListView; use Symfony\Component\Form\ChoiceList\View\ChoiceView; @@ -199,7 +200,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D') ); - $this->assertScalarListWithGeneratedValues($list); + $this->assertScalarListWithChoiceValues($list); } public function testCreateFromFlippedChoicesFlatTraversable() @@ -208,7 +209,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase new \ArrayIterator(array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D')) ); - $this->assertScalarListWithGeneratedValues($list); + $this->assertScalarListWithChoiceValues($list); } public function testCreateFromFlippedChoicesFlatValuesAsCallable() @@ -247,7 +248,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase ) ); - $this->assertScalarListWithGeneratedValues($list); + $this->assertScalarListWithChoiceValues($list); } public function testCreateFromFlippedChoicesGroupedTraversable() @@ -259,7 +260,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase )) ); - $this->assertScalarListWithGeneratedValues($list); + $this->assertScalarListWithChoiceValues($list); } public function testCreateFromFlippedChoicesGroupedValuesAsCallable() @@ -523,33 +524,16 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase $this->assertFlatViewWithCustomIndices($view); } - public function testCreateViewFlatGroupByAsArray() + public function testCreateViewFlatGroupByOriginalStructure() { - $view = $this->factory->createView( - $this->list, - array($this->obj2, $this->obj3), - null, // label - null, // index - array( - 'Group 1' => array('A' => true, 'B' => true), - 'Group 2' => array('C' => true, 'D' => true), - ) - ); + $list = new ArrayChoiceList(array( + 'Group 1' => array('A' => $this->obj1, 'B' => $this->obj2), + 'Group 2' => array('C' => $this->obj3, 'D' => $this->obj4), + )); - $this->assertGroupedView($view); - } - - public function testCreateViewFlatGroupByAsTraversable() - { $view = $this->factory->createView( - $this->list, - array($this->obj2, $this->obj3), - null, // label - null, // index - new \ArrayIterator(array( - 'Group 1' => array('A' => true, 'B' => true), - 'Group 2' => array('C' => true, 'D' => true), - )) + $list, + array($this->obj2, $this->obj3) ); $this->assertGroupedView($view); @@ -592,8 +576,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase null, // label null, // index function ($object) use ($obj1, $obj2) { - return $obj1 === $object || $obj2 === $object ? 'Group 1' - : 'Group 2'; + return $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2'; } ); @@ -749,78 +732,86 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase ->method('getRemainingViews') ->will($this->returnValue($other)); - $view = $this->factory->createView($list); + $view = $this->factory->createView(new LegacyChoiceListAdapter($list)); $this->assertEquals(array(new ChoiceView('y', 'y', 'Other')), $view->choices); $this->assertEquals(array(new ChoiceView('x', 'x', 'Preferred')), $view->preferredChoices); } - private function assertScalarListWithGeneratedValues(ChoiceListInterface $list) + private function assertScalarListWithChoiceValues(ChoiceListInterface $list) { + $this->assertSame(array('a', 'b', 'c', 'd'), $list->getValues()); + $this->assertSame(array( - 'A' => 'a', - 'B' => 'b', - 'C' => 'c', - 'D' => 'd', + 'a' => 'a', + 'b' => 'b', + 'c' => 'c', + 'd' => 'd', ), $list->getChoices()); $this->assertSame(array( - 'A' => 'a', - 'B' => 'b', - 'C' => 'c', - 'D' => 'd', - ), $list->getValues()); + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + ), $list->getOriginalKeys()); } private function assertObjectListWithGeneratedValues(ChoiceListInterface $list) { + $this->assertSame(array('0', '1', '2', '3'), $list->getValues()); + $this->assertSame(array( - 'A' => $this->obj1, - 'B' => $this->obj2, - 'C' => $this->obj3, - 'D' => $this->obj4, + 0 => $this->obj1, + 1 => $this->obj2, + 2 => $this->obj3, + 3 => $this->obj4, ), $list->getChoices()); $this->assertSame(array( - 'A' => '0', - 'B' => '1', - 'C' => '2', - 'D' => '3', - ), $list->getValues()); + 0 => 'A', + 1 => 'B', + 2 => 'C', + 3 => 'D', + ), $list->getOriginalKeys()); } private function assertScalarListWithCustomValues(ChoiceListInterface $list) { + $this->assertSame(array('a', 'b', '1', '2'), $list->getValues()); + $this->assertSame(array( - 'A' => 'a', - 'B' => 'b', - 'C' => 'c', - 'D' => 'd', + 'a' => 'a', + 'b' => 'b', + 1 => 'c', + 2 => 'd', ), $list->getChoices()); $this->assertSame(array( - 'A' => 'a', - 'B' => 'b', - 'C' => '1', - 'D' => '2', - ), $list->getValues()); + 'a' => 'A', + 'b' => 'B', + 1 => 'C', + 2 => 'D', + ), $list->getOriginalKeys()); } private function assertObjectListWithCustomValues(ChoiceListInterface $list) { + $this->assertSame(array('a', 'b', '1', '2'), $list->getValues()); + $this->assertSame(array( - 'A' => $this->obj1, - 'B' => $this->obj2, - 'C' => $this->obj3, - 'D' => $this->obj4, + 'a' => $this->obj1, + 'b' => $this->obj2, + 1 => $this->obj3, + 2 => $this->obj4, ), $list->getChoices()); $this->assertSame(array( - 'A' => 'a', - 'B' => 'b', - 'C' => '1', - 'D' => '2', - ), $list->getValues()); + 'a' => 'A', + 'b' => 'B', + 1 => 'C', + 2 => 'D', + ), $list->getOriginalKeys()); } private function assertFlatView($view) diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php index 2993721c82..5db96e6a7d 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/LazyChoiceListTest.php @@ -73,6 +73,36 @@ class LazyChoiceListTest extends \PHPUnit_Framework_TestCase $this->assertSame('RESULT', $this->list->getValues()); } + public function testGetStructuredValuesLoadsInnerListOnFirstCall() + { + $this->loader->expects($this->once()) + ->method('loadChoiceList') + ->with($this->value) + ->will($this->returnValue($this->innerList)); + + $this->innerList->expects($this->exactly(2)) + ->method('getStructuredValues') + ->will($this->returnValue('RESULT')); + + $this->assertSame('RESULT', $this->list->getStructuredValues()); + $this->assertSame('RESULT', $this->list->getStructuredValues()); + } + + public function testGetOriginalKeysLoadsInnerListOnFirstCall() + { + $this->loader->expects($this->once()) + ->method('loadChoiceList') + ->with($this->value) + ->will($this->returnValue($this->innerList)); + + $this->innerList->expects($this->exactly(2)) + ->method('getOriginalKeys') + ->will($this->returnValue('RESULT')); + + $this->assertSame('RESULT', $this->list->getOriginalKeys()); + $this->assertSame('RESULT', $this->list->getOriginalKeys()); + } + public function testGetChoicesForValuesForwardsCallIfListNotLoaded() { $this->loader->expects($this->exactly(2)) diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/LegacyChoiceListAdapterTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/LegacyChoiceListAdapterTest.php new file mode 100644 index 0000000000..521c950331 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/ChoiceList/LegacyChoiceListAdapterTest.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\ChoiceList; + +use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter; +use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface; + +/** + * @author Bernhard Schussek + */ +class LegacyChoiceListAdapterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var LegacyChoiceListAdapter + */ + private $list; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|ChoiceListInterface + */ + private $adaptedList; + + protected function setUp() + { + $this->adaptedList = $this->getMock('Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'); + $this->list = new LegacyChoiceListAdapter($this->adaptedList); + } + + public function testGetChoices() + { + $this->adaptedList->expects($this->once()) + ->method('getChoices') + ->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c')); + $this->adaptedList->expects($this->once()) + ->method('getValues') + ->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c')); + + $this->assertSame(array(':a' => 'a', ':b' => 'b', ':c' => 'c'), $this->list->getChoices()); + } + + public function testGetValues() + { + $this->adaptedList->expects($this->once()) + ->method('getChoices') + ->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c')); + $this->adaptedList->expects($this->once()) + ->method('getValues') + ->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c')); + + $this->assertSame(array(':a', ':b', ':c'), $this->list->getValues()); + } + + public function testGetStructuredValues() + { + $this->adaptedList->expects($this->once()) + ->method('getChoices') + ->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c')); + $this->adaptedList->expects($this->once()) + ->method('getValues') + ->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c')); + + $this->assertSame(array(1 => ':a', 4 => ':b', 7 => ':c'), $this->list->getStructuredValues()); + } + + public function testGetOriginalKeys() + { + $this->adaptedList->expects($this->once()) + ->method('getChoices') + ->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c')); + $this->adaptedList->expects($this->once()) + ->method('getValues') + ->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c')); + + $this->assertSame(array(':a' => 1, ':b' => 4, ':c' => 7), $this->list->getOriginalKeys()); + } + + public function testGetChoicesForValues() + { + $this->adaptedList->expects($this->once()) + ->method('getChoicesForValues') + ->with(array(1 => ':a', 4 => ':b', 7 => ':c')) + ->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c')); + + $this->assertSame(array(1 => 'a', 4 => 'b', 7 => 'c'), $this->list->getChoicesForValues(array(1 => ':a', 4 => ':b', 7 => ':c'))); + } + + public function testGetValuesForChoices() + { + $this->adaptedList->expects($this->once()) + ->method('getValuesForChoices') + ->with(array(1 => 'a', 4 => 'b', 7 => 'c')) + ->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c')); + + $this->assertSame(array(1 => ':a', 4 => ':b', 7 => ':c'), $this->list->getValuesForChoices(array(1 => 'a', 4 => 'b', 7 => 'c'))); + } + + public function testGetAdaptedList() + { + $this->assertSame($this->adaptedList, $this->list->getAdaptedList()); + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/FixRadioInputListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/FixRadioInputListenerTest.php index cb0b34b41f..b936ea35cc 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/FixRadioInputListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/FixRadioInputListenerTest.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Form\Tests\Extension\Core\EventListener; +use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener; -use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList; /** * @group legacy @@ -26,7 +26,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase { parent::setUp(); - $this->choiceList = new SimpleChoiceList(array('' => 'Empty', 0 => 'A', 1 => 'B')); + $this->choiceList = new ArrayKeyChoiceList(array('' => 'Empty', 0 => 'A', 1 => 'B')); } protected function tearDown() @@ -45,7 +45,6 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase $listener = new FixRadioInputListener($this->choiceList, true); $listener->preSubmit($event); - // Indices in SimpleChoiceList are zero-based generated integers $this->assertEquals(array(2 => '1'), $event->getData()); } @@ -58,7 +57,6 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase $listener = new FixRadioInputListener($this->choiceList, true); $listener->preSubmit($event); - // Indices in SimpleChoiceList are zero-based generated integers $this->assertEquals(array(1 => '0'), $event->getData()); } @@ -71,13 +69,12 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase $listener = new FixRadioInputListener($this->choiceList, true); $listener->preSubmit($event); - // Indices in SimpleChoiceList are zero-based generated integers $this->assertEquals(array(0 => ''), $event->getData()); } public function testConvertEmptyStringToPlaceholderIfNotFound() { - $list = new SimpleChoiceList(array(0 => 'A', 1 => 'B')); + $list = new ArrayKeyChoiceList(array(0 => 'A', 1 => 'B')); $data = ''; $form = $this->getMock('Symfony\Component\Form\Test\FormInterface'); @@ -91,7 +88,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase public function testDontConvertEmptyStringToPlaceholderIfNoPlaceholderUsed() { - $list = new SimpleChoiceList(array(0 => 'A', 1 => 'B')); + $list = new ArrayKeyChoiceList(array(0 => 'A', 1 => 'B')); $data = ''; $form = $this->getMock('Symfony\Component\Form\Test\FormInterface'); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 8e79b72199..32fa8b1af7 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -186,6 +186,32 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase } } + public function testExpandedChoicesOptionsAreFlattenedObjectChoices() + { + $obj1 = (object) array('id' => 1, 'name' => 'Bernhard'); + $obj2 = (object) array('id' => 2, 'name' => 'Fabien'); + $obj3 = (object) array('id' => 3, 'name' => 'Kris'); + $obj4 = (object) array('id' => 4, 'name' => 'Jon'); + $obj5 = (object) array('id' => 5, 'name' => 'Roman'); + + $form = $this->factory->create('choice', null, array( + 'expanded' => true, + 'choices' => array( + 'Symfony' => array($obj1, $obj2, $obj3), + 'Doctrine' => array($obj4, $obj5), + ), + 'choices_as_values' => true, + 'choice_name' => 'id', + )); + + $this->assertSame(5, $form->count(), 'Each nested choice should become a new field, not the groups'); + $this->assertTrue($form->has(1)); + $this->assertTrue($form->has(2)); + $this->assertTrue($form->has(3)); + $this->assertTrue($form->has(4)); + $this->assertTrue($form->has(5)); + } + public function testExpandedCheckboxesAreNeverRequired() { $form = $this->factory->create('choice', null, array( From 2e37ede7300d7421abe3d16d2167cc2899a75a74 Mon Sep 17 00:00:00 2001 From: Abdellatif Ait boudad Date: Fri, 19 Jun 2015 15:48:24 +0000 Subject: [PATCH 09/47] [Translation][update cmd] taken account into bundle overrides path. --- .../Command/TranslationUpdateCommand.php | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php index 5637c3a325..c2b29cbc18 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php +++ b/src/Symfony/Bundle/FrameworkBundle/Command/TranslationUpdateCommand.php @@ -67,6 +67,8 @@ EOF */ protected function execute(InputInterface $input, OutputInterface $output) { + $kernel = $this->getContainer()->get('kernel'); + // check presence of force or dump-message if ($input->getOption('force') !== true && $input->getOption('dump-messages') !== true) { $output->writeln('You must choose one of --force or --dump-messages'); @@ -85,18 +87,19 @@ EOF } // Define Root Path to App folder - $rootPath = $this->getApplication()->getKernel()->getRootDir(); - $currentName = "app folder"; + $transPaths = array($kernel->getRootDir().'/Resources/'); + $currentName = 'app folder'; // Override with provided Bundle info if (null !== $input->getArgument('bundle')) { - $foundBundle = $this->getApplication()->getKernel()->getBundle($input->getArgument('bundle')); - $rootPath = $foundBundle->getPath(); + $foundBundle = $kernel->getBundle($input->getArgument('bundle')); + $transPaths = array( + $foundBundle->getPath().'/Resources/', + sprintf('%s/Resources/%s/', $kernel->getRootDir(), $foundBundle->getName()), + ); $currentName = $foundBundle->getName(); } - // get bundle directory - $translationsPath = $rootPath.'/Resources/translations'; $output->writeln(sprintf('Generating "%s" translation files for "%s"', $input->getArgument('locale'), $currentName)); // load any messages from templates @@ -104,13 +107,23 @@ EOF $output->writeln('Parsing templates'); $extractor = $this->getContainer()->get('translation.extractor'); $extractor->setPrefix($input->getOption('prefix')); - $extractor->extract($rootPath.'/Resources/views/', $extractedCatalogue); + foreach ($transPaths as $path) { + $path = $path.'views'; + if (is_dir($path)) { + $extractor->extract($path, $extractedCatalogue); + } + } // load any existing messages from the translation files $currentCatalogue = new MessageCatalogue($input->getArgument('locale')); $output->writeln('Loading translation files'); $loader = $this->getContainer()->get('translation.loader'); - $loader->loadMessages($translationsPath, $currentCatalogue); + foreach ($transPaths as $path) { + $path = $path.'translations'; + if (is_dir($path)) { + $loader->loadMessages($path, $currentCatalogue); + } + } // process catalogues $operation = $input->getOption('clean') @@ -153,7 +166,17 @@ EOF // save the files if ($input->getOption('force') === true) { $output->writeln('Writing files'); - $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $translationsPath, 'default_locale' => $this->getContainer()->getParameter('kernel.default_locale'))); + $bundleTransPath = false; + foreach ($transPaths as $path) { + $path = $path.'translations'; + if (is_dir($path)) { + $bundleTransPath = $path; + } + } + + if ($bundleTransPath) { + $writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath)); + } } } } From 5571caa305aae40848307196437d95c501813fd0 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 22 Jun 2015 17:14:39 +0200 Subject: [PATCH 10/47] [Form] Fixed: remove quoted strings from Intl date formats (e.g. es_ES full pattern) --- .../Form/Extension/Core/Type/DateType.php | 4 ++++ .../Tests/Extension/Core/Type/DateTypeTest.php | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php index a7fc6cadf5..3e837ccef0 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -145,6 +145,10 @@ class DateType extends AbstractType // remove special characters unless the format was explicitly specified if (!is_string($options['format'])) { + // remove quoted strings first + $pattern = preg_replace('/\'[^\']+\'/', '', $pattern); + + // remove remaining special chars $pattern = preg_replace('/[^yMd]+/', '', $pattern); } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php index e5b58a8c4a..e5aba86efe 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php @@ -614,6 +614,20 @@ class DateTypeTest extends TestCase $this->assertFalse(isset($view->vars['date_pattern'])); } + public function testDatePatternFormatWithQuotedStrings() + { + \Locale::setDefault('es_ES'); + + $form = $this->factory->create('date', null, array( + // EEEE, d 'de' MMMM 'de' y + 'format' => \IntlDateFormatter::FULL, + )); + + $view = $form->createView(); + + $this->assertEquals('{{ day }}{{ month }}{{ year }}', $view->vars['date_pattern']); + } + public function testPassWidgetToView() { $form = $this->factory->create('date', null, array( From 497433cf03eb43cdceaf96a0f50e6aee5b9c955b Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 22 Jun 2015 16:39:08 +0200 Subject: [PATCH 11/47] [Form] Fixed: Support objects with __toString() in choice groups --- .../Factory/DefaultChoiceListFactory.php | 2 ++ .../Factory/DefaultChoiceListFactoryTest.php | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php index b7f37137d8..4fa757295a 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -329,6 +329,8 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface return; } + $groupLabel = (string) $groupLabel; + // Initialize the group views if necessary. Unnnecessarily built group // views will be cleaned up at the end of createView() if (!isset($preferredViews[$groupLabel])) { diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index e5112bf38f..34f9991e0d 100644 --- a/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -77,6 +77,13 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase return $this->obj1 === $object || $this->obj2 === $object ? 'Group 1' : 'Group 2'; } + public function getGroupAsObject($object) + { + return $this->obj1 === $object || $this->obj2 === $object + ? new DefaultChoiceListFactoryTest_Castable('Group 1') + : new DefaultChoiceListFactoryTest_Castable('Group 2'); + } + protected function setUp() { $this->obj1 = (object) array('label' => 'A', 'index' => 'w', 'value' => 'a', 'preferred' => false, 'group' => 'Group 1', 'attr' => array()); @@ -581,6 +588,19 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase $this->assertGroupedView($view); } + public function testCreateViewFlatGroupByObjectThatCanBeCastToString() + { + $view = $this->factory->createView( + $this->list, + array($this->obj2, $this->obj3), + null, // label + null, // index + array($this, 'getGroupAsObject') + ); + + $this->assertGroupedView($view); + } + public function testCreateViewFlatGroupByAsClosure() { $obj1 = $this->obj1; @@ -897,3 +917,18 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase ), $view); } } + +class DefaultChoiceListFactoryTest_Castable +{ + private $property; + + public function __construct($property) + { + $this->property = $property; + } + + public function __toString() + { + return $this->property; + } +} From 86b7fe590beb5595ce44ee40c6eec53412a3d7ff Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 22 Jun 2015 21:00:03 +0200 Subject: [PATCH 12/47] [Form] Fixed: Data mappers always receive forms indexed by their names --- src/Symfony/Component/Form/Form.php | 2 +- src/Symfony/Component/Form/Tests/CompoundFormTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php index 8a19f82a2d..de229064d5 100644 --- a/src/Symfony/Component/Form/Form.php +++ b/src/Symfony/Component/Form/Form.php @@ -895,7 +895,7 @@ class Form implements \IteratorAggregate, FormInterface $child->setParent($this); if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { - $iterator = new InheritDataAwareIterator(new \ArrayIterator(array($child))); + $iterator = new InheritDataAwareIterator(new \ArrayIterator(array($child->getName() => $child))); $iterator = new \RecursiveIteratorIterator($iterator); $this->config->getDataMapper()->mapDataToForms($viewData, $iterator); } diff --git a/src/Symfony/Component/Form/Tests/CompoundFormTest.php b/src/Symfony/Component/Form/Tests/CompoundFormTest.php index 5ebb9906fa..1f7b78889d 100644 --- a/src/Symfony/Component/Form/Tests/CompoundFormTest.php +++ b/src/Symfony/Component/Form/Tests/CompoundFormTest.php @@ -352,7 +352,7 @@ class CompoundFormTest extends AbstractFormTest ->with('bar', $this->isInstanceOf('\RecursiveIteratorIterator')) ->will($this->returnCallback(function ($data, \RecursiveIteratorIterator $iterator) use ($child, $test) { $test->assertInstanceOf('Symfony\Component\Form\Util\InheritDataAwareIterator', $iterator->getInnerIterator()); - $test->assertSame(array($child), iterator_to_array($iterator)); + $test->assertSame(array($child->getName() => $child), iterator_to_array($iterator)); })); $form->initialize(); From 8982c3246ca6c15e4b7a1cce5362d04c46d7f009 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 Jun 2015 13:38:55 +0200 Subject: [PATCH 13/47] [HttpFoundation] Use convention to allow throwing from __toString() --- src/Symfony/Component/HttpFoundation/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/HttpFoundation/Request.php b/src/Symfony/Component/HttpFoundation/Request.php index 424a2ed09f..8babb395c5 100644 --- a/src/Symfony/Component/HttpFoundation/Request.php +++ b/src/Symfony/Component/HttpFoundation/Request.php @@ -480,7 +480,7 @@ class Request try { $content = $this->getContent(); } catch (\LogicException $e) { - trigger_error($e->getMessage(), E_USER_ERROR); + return trigger_error($e, E_USER_ERROR); } return From 23c42ca33324a3ba13963585999072173ee8ed5a Mon Sep 17 00:00:00 2001 From: John Kary Date: Sun, 21 Jun 2015 18:29:54 -0500 Subject: [PATCH 14/47] [Console] Fix STDERR output text on IBM iSeries OS400 --- .../Console/Output/ConsoleOutput.php | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/Symfony/Component/Console/Output/ConsoleOutput.php b/src/Symfony/Component/Console/Output/ConsoleOutput.php index 3560f1c6fc..708d171445 100644 --- a/src/Symfony/Component/Console/Output/ConsoleOutput.php +++ b/src/Symfony/Component/Console/Output/ConsoleOutput.php @@ -30,6 +30,9 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface; */ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface { + /** + * @var StreamOutput + */ private $stderr; /** @@ -43,14 +46,12 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface */ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) { - $outputStream = 'php://stdout'; - if (!$this->hasStdoutSupport()) { - $outputStream = 'php://output'; - } + $outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output'; + $errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output'; parent::__construct(fopen($outputStream, 'w'), $verbosity, $decorated, $formatter); - $this->stderr = new StreamOutput(fopen('php://stderr', 'w'), $verbosity, $decorated, $this->getFormatter()); + $this->stderr = new StreamOutput(fopen($errorStream, 'w'), $verbosity, $decorated, $this->getFormatter()); } /** @@ -100,14 +101,32 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface * Returns true if current environment supports writing console output to * STDOUT. * - * IBM iSeries (OS400) exhibits character-encoding issues when writing to - * STDOUT and doesn't properly convert ASCII to EBCDIC, resulting in garbage - * output. - * * @return bool */ protected function hasStdoutSupport() { - return ('OS400' != php_uname('s')); + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Checks if current executing environment is IBM iSeries (OS400), which + * doesn't properly convert character-encodings between ASCII to EBCDIC. + * + * @return bool + */ + private function isRunningOS400() + { + return 'OS400' === php_uname('s'); } } From 18e37c834ef32f078f5a265b615e022c7fe77706 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 22 Jun 2015 17:38:12 +0200 Subject: [PATCH 15/47] [FrameworkBundle] Remove unused old_assets.xml --- .../Resources/config/old_assets.xml | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 src/Symfony/Bundle/FrameworkBundle/Resources/config/old_assets.xml diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/old_assets.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/old_assets.xml deleted file mode 100644 index 90d935906d..0000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/old_assets.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage - Symfony\Component\Templating\Asset\UrlPackage - Symfony\Bundle\FrameworkBundle\Templating\Asset\PackageFactory - - - - - - - - - - - - - - - - - - - - - - - - - - - From d37962fb03f36927fafa9564d28303b73d9ace9b Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Wed, 24 Jun 2015 15:11:21 +0200 Subject: [PATCH 16/47] Fixed the regexp for the validator of Maestro-based credit/debit cards --- .../Validator/Constraints/CardSchemeValidator.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php index 37efe89234..dccddee06a 100644 --- a/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CardSchemeValidator.php @@ -19,6 +19,7 @@ use Symfony\Component\Validator\ConstraintValidator; * * @see http://en.wikipedia.org/wiki/Bank_card_number * @see http://www.regular-expressions.info/creditcard.html + * @see http://www.barclaycard.co.uk/business/files/Ranges_and_Rules_September_2014.pdf * * @author Tim Nagel */ @@ -61,10 +62,12 @@ class CardSchemeValidator extends ConstraintValidator 'LASER' => array( '/^(6304|670[69]|6771)[0-9]{12,15}$/', ), - // Maestro cards begin with either 5018, 5020, 5038, 5893, 6304, 6759, 6761, 6762, 6763 or 0604 - // They have between 12 and 19 digits. + // Maestro international cards begin with 675900..675999 and have between 12 and 19 digits. + // Maestro UK cards begin with either 500000..509999 or 560000..699999 and have between 12 and 19 digits. 'MAESTRO' => array( - '/^(5018|5020|5038|6304|6759|6761|676[23]|0604)[0-9]{8,15}$/', + '/^(6759[0-9]{2})[0-9]{6,13}$/', + '/^(50[0-9]{4})[0-9]{6,13}$/', + '/^([56-69][0-9]{4})[0-9]{6,13}$/', ), // All MasterCard numbers start with the numbers 51 through 55. All have 16 digits. 'MASTERCARD' => array( From 4a1ad7e4c6ff3facc5dab091f552b8efeb176651 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 25 Jun 2015 12:30:32 +0200 Subject: [PATCH 17/47] [Form] Fixed compatibility with FormTypeInterface implementations that don't extend AbstractType --- .../Component/Form/ResolvedFormType.php | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/Symfony/Component/Form/ResolvedFormType.php b/src/Symfony/Component/Form/ResolvedFormType.php index 035ae52f1b..765cb7bf6c 100644 --- a/src/Symfony/Component/Form/ResolvedFormType.php +++ b/src/Symfony/Component/Form/ResolvedFormType.php @@ -200,27 +200,35 @@ class ResolvedFormType implements ResolvedFormTypeInterface $this->innerType->setDefaultOptions($this->optionsResolver); - $reflector = new \ReflectionMethod($this->innerType, 'setDefaultOptions'); - $isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType'; + if (method_exists($this->innerType, 'configureOptions')) { + $reflector = new \ReflectionMethod($this->innerType, 'setDefaultOptions'); + $isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType'; - $reflector = new \ReflectionMethod($this->innerType, 'configureOptions'); - $isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType'; + $reflector = new \ReflectionMethod($this->innerType, 'configureOptions'); + $isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType'; - if ($isOldOverwritten && !$isNewOverwritten) { - @trigger_error(get_class($this->innerType).': The FormTypeInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeInterface with Symfony 3.0.', E_USER_DEPRECATED); + if ($isOldOverwritten && !$isNewOverwritten) { + @trigger_error(get_class($this->innerType).': The FormTypeInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeInterface with Symfony 3.0.', E_USER_DEPRECATED); + } + } else { + @trigger_error(get_class($this->innerType).': The FormTypeInterface::configureOptions() method will be added in Symfony 3.0. You should extend AbstractType or implement it in your classes.', E_USER_DEPRECATED); } foreach ($this->typeExtensions as $extension) { $extension->setDefaultOptions($this->optionsResolver); - $reflector = new \ReflectionMethod($extension, 'setDefaultOptions'); - $isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension'; + if (method_exists($extension, 'configureOptions')) { + $reflector = new \ReflectionMethod($extension, 'setDefaultOptions'); + $isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension'; - $reflector = new \ReflectionMethod($extension, 'configureOptions'); - $isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension'; + $reflector = new \ReflectionMethod($extension, 'configureOptions'); + $isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension'; - if ($isOldOverwritten && !$isNewOverwritten) { - @trigger_error(get_class($extension).': The FormTypeExtensionInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeExtensionInterface with Symfony 3.0.', E_USER_DEPRECATED); + if ($isOldOverwritten && !$isNewOverwritten) { + @trigger_error(get_class($extension).': The FormTypeExtensionInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeExtensionInterface with Symfony 3.0.', E_USER_DEPRECATED); + } + } else { + @trigger_error(get_class($this->innerType).': The FormTypeExtensionInterface::configureOptions() method will be added in Symfony 3.0. You should extend AbstractTypeExtension or implement it in your classes.', E_USER_DEPRECATED); } } } From 33413153c99d7e1eef969c3a3e1f78523e8057e9 Mon Sep 17 00:00:00 2001 From: Abdellatif Ait boudad Date: Thu, 25 Jun 2015 12:14:41 +0000 Subject: [PATCH 18/47] [Translation][debug cmd] fixed failing tests. --- .../Tests/Command/TranslationDebugCommandTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php index b03d8fdfe7..aed3513d0c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationDebugCommandTest.php @@ -58,6 +58,7 @@ class TranslationDebugCommandTest extends \PHPUnit_Framework_TestCase $this->fs = new Filesystem(); $this->translationDir = sys_get_temp_dir().'/'.uniqid('sf2_translation'); $this->fs->mkdir($this->translationDir.'/Resources/translations'); + $this->fs->mkdir($this->translationDir.'/Resources/views'); } protected function tearDown() From 9e41fa7852a4a3c2966c843ec7e6c4433adc67de Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 26 Jun 2015 16:41:50 +0200 Subject: [PATCH 19/47] [DependencyInjection] improve deprecation messages Also include the service id in the deprecation message. --- .../DependencyInjection/Loader/XmlFileLoader.php | 4 ++-- .../DependencyInjection/Loader/YamlFileLoader.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index 523c01cea4..887f536e45 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -151,7 +151,7 @@ class XmlFileLoader extends FileLoader foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'lazy', 'abstract') as $key) { if ($value = $service->getAttribute($key)) { if (in_array($key, array('factory-class', 'factory-method', 'factory-service'))) { - @trigger_error(sprintf('The "%s" attribute in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use the "factory" element instead.', $key, $file), E_USER_DEPRECATED); + @trigger_error(sprintf('The "%s" attribute of service "%s" in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use the "factory" element instead.', $key, (string) $service->getAttribute('id'), $file), E_USER_DEPRECATED); } $method = 'set'.str_replace('-', '', $key); $definition->$method(XmlUtils::phpize($value)); @@ -162,7 +162,7 @@ class XmlFileLoader extends FileLoader $triggerDeprecation = 'request' !== (string) $service->getAttribute('id'); if ($triggerDeprecation) { - @trigger_error(sprintf('The "synchronized" attribute in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', $file), E_USER_DEPRECATED); + @trigger_error(sprintf('The "synchronized" attribute of service "%s" in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', (string) $service->getAttribute('id'), $file), E_USER_DEPRECATED); } $definition->setSynchronized(XmlUtils::phpize($value), $triggerDeprecation); diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index e10cfe2a4b..be2d3f1300 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -172,7 +172,7 @@ class YamlFileLoader extends FileLoader } if (isset($service['synchronized'])) { - @trigger_error(sprintf('The "synchronized" key in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', $file), E_USER_DEPRECATED); + @trigger_error(sprintf('The "synchronized" key of service "%s" in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', $id, $file), E_USER_DEPRECATED); $definition->setSynchronized($service['synchronized'], 'request' !== $id); } @@ -202,17 +202,17 @@ class YamlFileLoader extends FileLoader } if (isset($service['factory_class'])) { - @trigger_error(sprintf('The "factory_class" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED); + @trigger_error(sprintf('The "factory_class" key of service "%s" in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $id, $file), E_USER_DEPRECATED); $definition->setFactoryClass($service['factory_class']); } if (isset($service['factory_method'])) { - @trigger_error(sprintf('The "factory_method" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED); + @trigger_error(sprintf('The "factory_method" key of service "%s" in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $id, $file), E_USER_DEPRECATED); $definition->setFactoryMethod($service['factory_method']); } if (isset($service['factory_service'])) { - @trigger_error(sprintf('The "factory_service" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED); + @trigger_error(sprintf('The "factory_service" key of service "%s" in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $id, $file), E_USER_DEPRECATED); $definition->setFactoryService($service['factory_service']); } From 96a5653c8d9b90868e6646028891ca8774783b96 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 26 Jun 2015 18:55:11 +0200 Subject: [PATCH 20/47] update type hint --- .../Tests/DependencyInjection/FrameworkExtensionTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 57b6e651b0..213f092707 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -14,7 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Reference; @@ -525,14 +525,14 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertUrlPackage($container, $package, array('https://bar2.example.com'), $legacy ? '' : 'SomeVersionScheme', $legacy ? '%%s?%%s' : '%%s?version=%%s'); } - private function assertPathPackage(ContainerBuilder $container, Definition $package, $basePath, $version, $format) + private function assertPathPackage(ContainerBuilder $container, DefinitionDecorator $package, $basePath, $version, $format) { $this->assertEquals('assets.path_package', $package->getParent()); $this->assertEquals($basePath, $package->getArgument(0)); $this->assertVersionStrategy($container, $package->getArgument(1), $version, $format); } - private function assertUrlPackage(ContainerBuilder $container, Definition $package, $baseUrls, $version, $format) + private function assertUrlPackage(ContainerBuilder $container, DefinitionDecorator $package, $baseUrls, $version, $format) { $this->assertEquals('assets.url_package', $package->getParent()); $this->assertEquals($baseUrls, $package->getArgument(0)); From 0f185c9fef0588ae963cae4ca9c5e77a6449607a Mon Sep 17 00:00:00 2001 From: BruceWouaigne Date: Thu, 5 Mar 2015 18:38:17 +0100 Subject: [PATCH 21/47] [Form] [EventListener] fixed sending non array data on submit to ResizeListener --- .../Extension/Core/EventListener/ResizeFormListener.php | 2 +- .../Core/EventListener/ResizeFormListenerTest.php | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php index 8a4919240d..01c3c1b0b2 100644 --- a/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php +++ b/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php @@ -101,7 +101,7 @@ class ResizeFormListener implements EventSubscriberInterface } if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { - throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)'); + $data = array(); } // Remove all empty rows diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php index 7bcc7f2d12..7197666392 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/EventListener/ResizeFormListenerTest.php @@ -167,15 +167,14 @@ class ResizeFormListenerTest extends \PHPUnit_Framework_TestCase $this->assertFalse($this->form->has('2')); } - /** - * @expectedException \Symfony\Component\Form\Exception\UnexpectedTypeException - */ - public function testPreSubmitRequiresArrayOrTraversable() + public function testPreSubmitDealsWithNoArrayOrTraversable() { $data = 'no array or traversable'; $event = new FormEvent($this->form, $data); $listener = new ResizeFormListener('text', array(), false, false); $listener->preSubmit($event); + + $this->assertFalse($this->form->has('1')); } public function testPreSubmitDealsWithNullData() From 26c4413f293572f14ce9bf854e93fe0030666f61 Mon Sep 17 00:00:00 2001 From: Massimiliano Arione Date: Fri, 26 Jun 2015 14:13:57 +0200 Subject: [PATCH 22/47] Fix typo in Italian translation --- .../Validator/Resources/translations/validators.it.xlf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf b/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf index 8b765bf642..8366dcbb75 100644 --- a/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf +++ b/src/Symfony/Component/Validator/Resources/translations/validators.it.xlf @@ -40,7 +40,7 @@ This field is missing. - Questo campo è manca. + Questo campo è mancante. This value is not a valid date. From 62f12cde7a9056cc8e288c3bb20ffaf8ee0c591a Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Tue, 16 Jun 2015 23:07:21 +0200 Subject: [PATCH 23/47] [Validator] don't trigger deprecation with empty group array --- .../RecursiveValidator2Dot5ApiTest.php | 25 +++++++++++++++++++ .../Validator/RecursiveValidator.php | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php index 05961269a5..b27e6454be 100644 --- a/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php +++ b/src/Symfony/Component/Validator/Tests/Validator/RecursiveValidator2Dot5ApiTest.php @@ -15,6 +15,7 @@ use Symfony\Component\Translation\IdentityTranslator; use Symfony\Component\Validator\ConstraintValidatorFactory; use Symfony\Component\Validator\Context\ExecutionContextFactory; use Symfony\Component\Validator\MetadataFactoryInterface; +use Symfony\Component\Validator\Tests\Fixtures\Entity; use Symfony\Component\Validator\Validator\RecursiveValidator; class RecursiveValidator2Dot5ApiTest extends Abstract2Dot5ApiTest @@ -29,4 +30,28 @@ class RecursiveValidator2Dot5ApiTest extends Abstract2Dot5ApiTest return new RecursiveValidator($contextFactory, $metadataFactory, $validatorFactory, $objectInitializers); } + + public function testEmptyGroupsArrayDoesNotTriggerDeprecation() + { + $entity = new Entity(); + + $validatorContext = $this->getMock('Symfony\Component\Validator\Validator\ContextualValidatorInterface'); + $validatorContext + ->expects($this->once()) + ->method('validate') + ->with($entity, null, array()) + ->willReturnSelf(); + + $validator = $this + ->getMockBuilder('Symfony\Component\Validator\Validator\RecursiveValidator') + ->disableOriginalConstructor() + ->setMethods(array('startContext')) + ->getMock(); + $validator + ->expects($this->once()) + ->method('startContext') + ->willReturn($validatorContext); + + $validator->validate($entity, null, array()); + } } diff --git a/src/Symfony/Component/Validator/Validator/RecursiveValidator.php b/src/Symfony/Component/Validator/Validator/RecursiveValidator.php index 2d66a23fe3..2165ef0ff1 100644 --- a/src/Symfony/Component/Validator/Validator/RecursiveValidator.php +++ b/src/Symfony/Component/Validator/Validator/RecursiveValidator.php @@ -25,6 +25,7 @@ use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface; * Recursive implementation of {@link ValidatorInterface}. * * @since 2.5 + * * @author Bernhard Schussek */ class RecursiveValidator implements ValidatorInterface, LegacyValidatorInterface @@ -182,6 +183,6 @@ class RecursiveValidator implements ValidatorInterface, LegacyValidatorInterface 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)); + return null === $groups || is_string($groups) || $groups instanceof GroupSequence || (is_array($groups) && (0 === count($groups) || is_string(current($groups)) || current($groups) instanceof GroupSequence)); } } From bd49f23bb1e2c03ebde56a59fc7479b6d371edb5 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Fri, 26 Jun 2015 22:46:46 +0200 Subject: [PATCH 24/47] [Console] respect multi-character shortcuts The `TextDescriptor` assumed that shortcuts will only consume one space when calculating the maximum width needed to display the option's synopsis. --- .../Component/Console/Descriptor/TextDescriptor.php | 9 +++++---- .../Console/Tests/Descriptor/ObjectsProvider.php | 1 + .../Component/Console/Tests/Fixtures/input_option_6.json | 1 + .../Component/Console/Tests/Fixtures/input_option_6.md | 9 +++++++++ .../Component/Console/Tests/Fixtures/input_option_6.txt | 1 + .../Component/Console/Tests/Fixtures/input_option_6.xml | 5 +++++ 6 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_6.json create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_6.md create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_6.txt create mode 100644 src/Symfony/Component/Console/Tests/Fixtures/input_option_6.xml diff --git a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php index 0824463fce..5c3fea9a2c 100644 --- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php +++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php @@ -40,7 +40,7 @@ class TextDescriptor extends Descriptor $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; - $this->writeText(sprintf(" %s%s%s%s", + $this->writeText(sprintf(' %s%s%s%s', $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces @@ -77,7 +77,7 @@ class TextDescriptor extends Descriptor $spacingWidth = $totalWidth - strlen($synopsis) + 2; - $this->writeText(sprintf(" %s%s%s%s%s", + $this->writeText(sprintf(' %s%s%s%s%s', $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces @@ -207,7 +207,7 @@ class TextDescriptor extends Descriptor foreach ($namespace['commands'] as $name) { $this->writeText("\n"); $spacingWidth = $width - strlen($name); - $this->writeText(sprintf(" %s%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options); + $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options); } } @@ -266,7 +266,8 @@ class TextDescriptor extends Descriptor { $totalWidth = 0; foreach ($options as $option) { - $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + -- + // "-" + shortcut + ", --" + name + $nameLength = 1 + max(strlen($option->getShortcut()), 1) + 4 + strlen($option->getName()); if ($option->acceptValue()) { $valueLength = 1 + strlen($option->getName()); // = + value diff --git a/src/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php b/src/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php index 1f40d2e6a0..45b3b2fff9 100644 --- a/src/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php +++ b/src/Symfony/Component/Console/Tests/Descriptor/ObjectsProvider.php @@ -42,6 +42,7 @@ class ObjectsProvider 'input_option_3' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description'), 'input_option_4' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'option description', array()), 'input_option_5' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, "multiline\noption description"), + 'input_option_6' => new InputOption('option_name', array('o', 'O'), InputOption::VALUE_REQUIRED, 'option with multiple shortcuts'), ); } diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.json b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.json new file mode 100644 index 0000000000..d84e872147 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.json @@ -0,0 +1 @@ +{"name":"--option_name","shortcut":"-o|-O","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"option with multiple shortcuts","default":null} diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.md b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.md new file mode 100644 index 0000000000..ed1ea1c84b --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.md @@ -0,0 +1,9 @@ +**option_name:** + +* Name: `--option_name` +* Shortcut: `-o|-O` +* Accept value: yes +* Is value required: yes +* Is multiple: no +* Description: option with multiple shortcuts +* Default: `NULL` diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.txt b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.txt new file mode 100644 index 0000000000..0e6c9759b5 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.txt @@ -0,0 +1 @@ + -o|O, --option_name=OPTION_NAME option with multiple shortcuts diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.xml b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.xml new file mode 100644 index 0000000000..06126a2f57 --- /dev/null +++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_6.xml @@ -0,0 +1,5 @@ + + From 51212acbff3ac12a85f6cf7ca4ff6cb80b4eb3f5 Mon Sep 17 00:00:00 2001 From: Abdellatif Ait boudad Date: Wed, 10 Jun 2015 14:31:20 +0000 Subject: [PATCH 25/47] [Validator][callback constraint] include class name in ConstraintDefinitionException. --- .../Component/Validator/Constraints/CallbackValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php index 9939306cb8..eaf2f3c011 100644 --- a/src/Symfony/Component/Validator/Constraints/CallbackValidator.php +++ b/src/Symfony/Component/Validator/Constraints/CallbackValidator.php @@ -60,7 +60,7 @@ class CallbackValidator extends ConstraintValidator call_user_func($method, $object, $this->context); } elseif (null !== $object) { if (!method_exists($object, $method)) { - throw new ConstraintDefinitionException(sprintf('Method "%s" targeted by Callback constraint does not exist', $method)); + throw new ConstraintDefinitionException(sprintf('Method "%s" targeted by Callback constraint does not exist in class %s', $method, get_class($object))); } $reflMethod = new \ReflectionMethod($object, $method); From 1a5c4c6c93d9db0fcdc277e25df8f704b6ce0a28 Mon Sep 17 00:00:00 2001 From: Restless-ET Date: Tue, 16 Jun 2015 10:52:18 +0100 Subject: [PATCH 26/47] [Translation][Form][choice] empty_value shouldn't be translated when it has an empty value --- .../Resources/views/Form/form_div_layout.html.twig | 2 +- .../views/Form/choice_widget_collapsed.html.php | 2 +- .../Component/Form/Tests/AbstractLayoutTest.php | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig index 3025450d79..243c9b1879 100644 --- a/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig +++ b/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig @@ -57,7 +57,7 @@ {%- endif -%}