diff --git a/.travis.yml b/.travis.yml
index a527f770d5..e8f3245d37 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -87,7 +87,7 @@ install:
- export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev
- if [[ ! $skip && $deps ]]; then export SYMFONY_DEPRECATIONS_HELPER=weak; fi
- if [[ ! $skip && $deps ]]; then mv composer.json.phpunit composer.json; fi
- - if [[ ! $skip ]]; then composer update; fi
+ - if [[ ! $skip ]]; then composer update --no-suggest; fi
- if [[ ! $skip ]]; then ./phpunit install; fi
- if [[ ! $skip && ! $PHP = hhvm* ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi
@@ -98,8 +98,8 @@ script:
- if [[ ! $deps && ! $PHP = hhvm* ]]; then echo -e "\\nRunning tests requiring tty"; $PHPUNIT --group tty; fi
- if [[ ! $deps && $PHP = hhvm* ]]; then $PHPUNIT --exclude-group benchmark,intl-data; fi
- if [[ ! $deps && $PHP = ${MIN_PHP%.*} ]]; then echo -e "1\\n0" | xargs -I{} sh -c 'echo "\\nPHP --enable-sigchild enhanced={}" && SYMFONY_DEPRECATIONS_HELPER=weak ENHANCE_SIGCHLD={} php-$MIN_PHP/sapi/cli/php .phpunit/phpunit-4.8/phpunit --colors=always src/Symfony/Component/Process/'; fi
- - if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --no-progress --ansi; $PHPUNIT --exclude-group tty,benchmark,intl-data'$LEGACY"$REPORT"; fi
- - if [[ $deps = low ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --no-progress --ansi --prefer-lowest --prefer-stable; $PHPUNIT --exclude-group tty,benchmark,intl-data'"$REPORT"; fi
+ - if [[ $deps = high ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --no-progress --no-suggest --ansi; $PHPUNIT --exclude-group tty,benchmark,intl-data'$LEGACY"$REPORT"; fi
+ - if [[ $deps = low ]]; then echo "$COMPONENTS" | parallel --gnu -j10% 'cd {}; composer update --no-progress --no-suggest --ansi --prefer-lowest --prefer-stable; $PHPUNIT --exclude-group tty,benchmark,intl-data'"$REPORT"; fi
# Test the PhpUnit bridge using the original phpunit script
- if [[ $deps = low ]]; then (cd src/Symfony/Bridge/PhpUnit && wget https://phar.phpunit.de/phpunit-4.8.phar); fi
- if [[ $deps = low ]]; then (cd src/Symfony/Bridge/PhpUnit && phpenv global 5.3 && php --version && composer update && php phpunit-4.8.phar); fi
diff --git a/appveyor.yml b/appveyor.yml
index d5c663d5a0..79be565e0d 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -51,7 +51,7 @@ install:
- copy /Y .composer\* %APPDATA%\Composer\
- php .github/build-packages.php "HEAD^" src\Symfony\Bridge\PhpUnit
- IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev)
- - php composer.phar update --no-progress --ansi
+ - php composer.phar update --no-progress --no-suggest --ansi
- php phpunit install
test_script:
diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php
index 9477d82655..f199f16265 100644
--- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php
+++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/DoctrineChoiceLoader.php
@@ -61,9 +61,9 @@ class DoctrineChoiceLoader implements ChoiceLoaderInterface
* loaded objects
* @param IdReader $idReader The reader for the object
* IDs.
+ * @param null|EntityLoaderInterface $objectLoader The objects loader
* @param ChoiceListFactoryInterface $factory The factory for creating
* the loaded choice list
- * @param null|EntityLoaderInterface $objectLoader The objects loader
*/
public function __construct($manager, $class, $idReader = null, $objectLoader = null, $factory = null)
{
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php
index d811421b00..33092d3646 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php
+++ b/src/Symfony/Bundle/WebProfilerBundle/Controller/ProfilerController.php
@@ -40,12 +40,13 @@ class ProfilerController
/**
* Constructor.
*
- * @param UrlGeneratorInterface $generator The URL Generator
- * @param Profiler $profiler The profiler
- * @param \Twig_Environment $twig The twig environment
- * @param array $templates The templates
- * @param string $toolbarPosition The toolbar position (top, bottom, normal, or null -- use the configuration)
- * @param string $baseDir The project root directory
+ * @param UrlGeneratorInterface $generator The URL Generator
+ * @param Profiler $profiler The profiler
+ * @param \Twig_Environment $twig The twig environment
+ * @param array $templates The templates
+ * @param string $toolbarPosition The toolbar position (top, bottom, normal, or null -- use the configuration)
+ * @param ContentSecurityPolicyHandler $cspHandler The Content-Security-Policy handler
+ * @param string $baseDir The project root directory
*/
public function __construct(UrlGeneratorInterface $generator, Profiler $profiler = null, \Twig_Environment $twig, array $templates, $toolbarPosition = 'bottom', ContentSecurityPolicyHandler $cspHandler = null, $baseDir = null)
{
diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php
index 760cb8365e..a40c42c88e 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php
+++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/WebDebugToolbarListener.php
@@ -71,7 +71,7 @@ class WebDebugToolbarListener implements EventSubscriberInterface
$this->urlGenerator->generate('_profiler', array('token' => $response->headers->get('X-Debug-Token')), UrlGeneratorInterface::ABSOLUTE_URL)
);
} catch (\Exception $e) {
- $response->headers->set('X-Debug-Error', get_class($e).': '.$e->getMessage());
+ $response->headers->set('X-Debug-Error', get_class($e).': '.preg_replace('/\s+/', ' ', $e->getMessage()));
}
}
diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php
index da7495c8c4..dff05c182c 100644
--- a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php
+++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/WebDebugToolbarListenerTest.php
@@ -246,6 +246,27 @@ class WebDebugToolbarListenerTest extends TestCase
$this->assertEquals('Exception: foo', $response->headers->get('X-Debug-Error'));
}
+ public function testThrowingErrorCleanup()
+ {
+ $response = new Response();
+ $response->headers->set('X-Debug-Token', 'xxxxxxxx');
+
+ $urlGenerator = $this->getUrlGeneratorMock();
+ $urlGenerator
+ ->expects($this->once())
+ ->method('generate')
+ ->with('_profiler', array('token' => 'xxxxxxxx'))
+ ->will($this->throwException(new \Exception("This\nmultiline\r\ntabbed text should\tcome out\r on\n \ta single plain\r\nline")))
+ ;
+
+ $event = new FilterResponseEvent($this->getKernelMock(), $this->getRequestMock(), HttpKernelInterface::MASTER_REQUEST, $response);
+
+ $listener = new WebDebugToolbarListener($this->getTwigMock(), false, WebDebugToolbarListener::ENABLED, 'bottom', $urlGenerator);
+ $listener->onKernelResponse($event);
+
+ $this->assertEquals('Exception: This multiline tabbed text should come out on a single plain line', $response->headers->get('X-Debug-Error'));
+ }
+
protected function getRequestMock($isXmlHttpRequest = false, $requestFormat = 'html', $hasSession = true)
{
$request = $this->getMockBuilder('Symfony\Component\HttpFoundation\Request')->setMethods(array('getSession', 'isXmlHttpRequest', 'getRequestFormat'))->disableOriginalConstructor()->getMock();
diff --git a/src/Symfony/Component/Asset/PathPackage.php b/src/Symfony/Component/Asset/PathPackage.php
index 906879f8b0..a8785c53f4 100644
--- a/src/Symfony/Component/Asset/PathPackage.php
+++ b/src/Symfony/Component/Asset/PathPackage.php
@@ -31,6 +31,7 @@ class PathPackage extends Package
/**
* @param string $basePath The base path to be prepended to relative paths
* @param VersionStrategyInterface $versionStrategy The version strategy
+ * @param ContextInterface|null $context The context
*/
public function __construct($basePath, VersionStrategyInterface $versionStrategy, ContextInterface $context = null)
{
diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php
index f6b523a74b..8ddfa27f38 100644
--- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php
@@ -57,7 +57,8 @@ class PhpArrayAdapter implements AdapterInterface
* stores arrays in its latest versions. This factory method decorates the given
* fallback pool with this adapter only if the current PHP version is supported.
*
- * @param string $file The PHP file were values are cached
+ * @param string $file The PHP file were values are cached
+ * @param CacheItemPoolInterface $fallbackPool Fallback for old PHP versions or opcache disabled
*
* @return CacheItemPoolInterface
*/
diff --git a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php
index 75cb764f40..ae718ca954 100644
--- a/src/Symfony/Component/Cache/Adapter/RedisAdapter.php
+++ b/src/Symfony/Component/Cache/Adapter/RedisAdapter.php
@@ -18,7 +18,9 @@ class RedisAdapter extends AbstractAdapter
use RedisTrait;
/**
- * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient
+ * @param \Redis|\RedisArray|\RedisCluster|\Predis\Client $redisClient The redis client
+ * @param string $namespace The default namespace
+ * @param integer $defaultLifetime The default lifetime
*/
public function __construct($redisClient, $namespace = '', $defaultLifetime = 0)
{
diff --git a/src/Symfony/Component/Console/Application.php b/src/Symfony/Component/Console/Application.php
index 73bea791a9..0ab3c3e0e3 100644
--- a/src/Symfony/Component/Console/Application.php
+++ b/src/Symfony/Component/Console/Application.php
@@ -672,12 +672,11 @@ class Application
if (defined('HHVM_VERSION') && $width > 1 << 31) {
$width = 1 << 31;
}
- $formatter = $output->getFormatter();
$lines = array();
- foreach (preg_split('/\r?\n/', OutputFormatter::escape($e->getMessage())) as $line) {
+ foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) {
foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
// pre-format lines to get the right string length
- $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4;
+ $lineLength = $this->stringWidth($line) + 4;
$lines[] = array($line, $lineLength);
$len = max($lineLength, $len);
@@ -685,15 +684,15 @@ class Application
}
$messages = array();
- $messages[] = $emptyLine = $formatter->format(sprintf('%s', str_repeat(' ', $len)));
- $messages[] = $formatter->format(sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))));
+ $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len));
+ $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title))));
foreach ($lines as $line) {
- $messages[] = $formatter->format(sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1])));
+ $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
}
$messages[] = $emptyLine;
$messages[] = '';
- $output->writeln($messages, OutputInterface::OUTPUT_RAW | OutputInterface::VERBOSITY_QUIET);
+ $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET);
diff --git a/src/Symfony/Component/Console/Helper/Helper.php b/src/Symfony/Component/Console/Helper/Helper.php
index 1124f80ef3..44bc2da239 100644
--- a/src/Symfony/Component/Console/Helper/Helper.php
+++ b/src/Symfony/Component/Console/Helper/Helper.php
@@ -123,6 +123,11 @@ abstract class Helper implements HelperInterface
}
public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string)
+ {
+ return self::strlen(self::removeDecoration($formatter, $string));
+ }
+
+ public static function removeDecoration(OutputFormatterInterface $formatter, $string)
{
$isDecorated = $formatter->isDecorated();
$formatter->setDecorated(false);
@@ -132,6 +137,6 @@ abstract class Helper implements HelperInterface
$string = preg_replace("/\033\[[^m]*m/", '', $string);
$formatter->setDecorated($isDecorated);
- return self::strlen($string);
+ return $string;
}
}
diff --git a/src/Symfony/Component/Console/Helper/Table.php b/src/Symfony/Component/Console/Helper/Table.php
index 36f3301817..66962c94d8 100644
--- a/src/Symfony/Component/Console/Helper/Table.php
+++ b/src/Symfony/Component/Console/Helper/Table.php
@@ -426,7 +426,7 @@ class Table
if (!strstr($cell, "\n")) {
continue;
}
- $lines = explode("\n", $cell);
+ $lines = explode("\n", str_replace("\n", "\n>", $cell));
foreach ($lines as $lineKey => $line) {
if ($cell instanceof TableCell) {
$line = new TableCell($line, array('colspan' => $cell->getColspan()));
@@ -467,7 +467,7 @@ class Table
$nbLines = $cell->getRowspan() - 1;
$lines = array($cell);
if (strstr($cell, "\n")) {
- $lines = explode("\n", $cell);
+ $lines = explode("\n", str_replace("\n", "\n>", $cell));
$nbLines = count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
$rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan()));
@@ -602,9 +602,10 @@ class Table
foreach ($row as $i => $cell) {
if ($cell instanceof TableCell) {
- $textLength = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
+ $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
+ $textLength = Helper::strlen($textContent);
if ($textLength > 0) {
- $contentColumns = str_split($cell, ceil($textLength / $cell->getColspan()));
+ $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
foreach ($contentColumns as $position => $content) {
$row[$i + $position] = $content;
}
diff --git a/src/Symfony/Component/Console/Tester/CommandTester.php b/src/Symfony/Component/Console/Tester/CommandTester.php
index 080ace5c95..0bb1603c33 100644
--- a/src/Symfony/Component/Console/Tester/CommandTester.php
+++ b/src/Symfony/Component/Console/Tester/CommandTester.php
@@ -76,9 +76,7 @@ class CommandTester
}
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
- if (isset($options['decorated'])) {
- $this->output->setDecorated($options['decorated']);
- }
+ $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false);
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}
diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php
index fa583d99af..314165a195 100644
--- a/src/Symfony/Component/Console/Tests/ApplicationTest.php
+++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php
@@ -636,6 +636,22 @@ class ApplicationTest extends TestCase
putenv('COLUMNS=120');
}
+ public function testRenderExceptionEscapesLines()
+ {
+ $application = $this->getMockBuilder('Symfony\Component\Console\Application')->setMethods(array('getTerminalWidth'))->getMock();
+ $application->setAutoExit(false);
+ $application->expects($this->any())
+ ->method('getTerminalWidth')
+ ->will($this->returnValue(22));
+ $application->register('foo')->setCode(function () {
+ throw new \Exception('dont break here !');
+ });
+ $tester = new ApplicationTester($application);
+
+ $tester->run(array('command' => 'foo'), array('decorated' => false));
+ $this->assertStringEqualsFile(self::$fixturesPath.'/application_renderexception_escapeslines.txt', $tester->getDisplay(true), '->renderException() escapes lines containing formatting');
+ }
+
public function testRun()
{
$application = new Application();
diff --git a/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_escapeslines.txt b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_escapeslines.txt
new file mode 100644
index 0000000000..cf79b37a92
--- /dev/null
+++ b/src/Symfony/Component/Console/Tests/Fixtures/application_renderexception_escapeslines.txt
@@ -0,0 +1,9 @@
+
+
+ [Exception]
+ dont break here <
+ info>!
+
+
+foo
+
diff --git a/src/Symfony/Component/Console/Tests/Helper/TableTest.php b/src/Symfony/Component/Console/Tests/Helper/TableTest.php
index 89b8efffb6..d8a8ff0087 100644
--- a/src/Symfony/Component/Console/Tests/Helper/TableTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/TableTest.php
@@ -511,6 +511,35 @@ TABLE
| Dante Alighieri | J. R. R. Tolkien | J. R. R |
+-----------------+------------------+---------+
+TABLE
+ ,
+ true,
+ ),
+ 'Row with formatted cells containing a newline' => array(
+ array(),
+ array(
+ array(
+ new TableCell('Dont break'."\n".'here', array('colspan' => 2)),
+ ),
+ new TableSeparator(),
+ array(
+ 'foo',
+ new TableCell('Dont break'."\n".'here', array('rowspan' => 2)),
+ ),
+ array(
+ 'bar',
+ ),
+ ),
+ 'default',
+ <<<'TABLE'
++-------+------------+
+[39;49m| [39;49m[37;41mDont break[39;49m[39;49m |[39;49m
+[39;49m| [39;49m[37;41mhere[39;49m |
++-------+------------+
+[39;49m| foo | [39;49m[37;41mDont break[39;49m[39;49m |[39;49m
+[39;49m| bar | [39;49m[37;41mhere[39;49m |
++-------+------------+
+
TABLE
,
true,
diff --git a/src/Symfony/Component/Debug/ExceptionHandler.php b/src/Symfony/Component/Debug/ExceptionHandler.php
index 2fdd8456fd..f2ae4b102e 100644
--- a/src/Symfony/Component/Debug/ExceptionHandler.php
+++ b/src/Symfony/Component/Debug/ExceptionHandler.php
@@ -83,7 +83,7 @@ class ExceptionHandler
/**
* Sets the format for links to source files.
*
- * @param string|FileLinkFormatter $format The format for links to source files
+ * @param string|FileLinkFormatter $fileLinkFormat The format for links to source files
*
* @return string The previous file link format
*/
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
index 59019f634b..d35d38569c 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php
@@ -30,7 +30,7 @@ class AutowirePass extends AbstractRecursivePass
private $definedTypes = array();
private $types;
private $ambiguousServiceTypes = array();
- private $usedTypes = array();
+ private $autowired = array();
private $currentDefinition;
/**
@@ -40,27 +40,11 @@ class AutowirePass extends AbstractRecursivePass
{
try {
parent::process($container);
-
- foreach ($this->usedTypes as $type => $id) {
- if (!isset($this->usedTypes[$type]) || !isset($this->ambiguousServiceTypes[$type])) {
- continue;
- }
-
- if ($container->has($type) && !$container->findDefinition($type)->isAbstract()) {
- continue;
- }
-
- $this->container = $container;
- $classOrInterface = class_exists($type, false) ? 'class' : 'interface';
-
- throw new RuntimeException(sprintf('Cannot autowire service "%s": multiple candidate services exist for %s "%s".%s', $id, $classOrInterface, $type, $this->createTypeAlternatives($type)));
- }
} finally {
- $this->container = null;
$this->definedTypes = array();
$this->types = null;
$this->ambiguousServiceTypes = array();
- $this->usedTypes = array();
+ $this->autowired = array();
}
}
@@ -111,6 +95,9 @@ class AutowirePass extends AbstractRecursivePass
if (!$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
return parent::processValue($value, $isRoot);
}
+ if ($value->getFactory()) {
+ throw new RuntimeException(sprintf('Service "%s" can use either autowiring or a factory, not both.', $this->currentId));
+ }
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass())) {
$this->container->log($this, sprintf('Skipping service "%s": Class or interface "%s" does not exist.', $this->currentId, $value->getClass()));
@@ -250,37 +237,38 @@ class AutowirePass extends AbstractRecursivePass
if (!$isConstructor && !$arguments && !$reflectionMethod->getNumberOfRequiredParameters()) {
throw new RuntimeException(sprintf('Cannot autowire service "%s": method %s() has only optional arguments, thus must be wired explicitly.', $this->currentId, $class !== $this->currentId ? $class.'::'.$method : $method));
}
+ $parameters = $reflectionMethod->getParameters();
+ if (method_exists('ReflectionMethod', 'isVariadic') && $reflectionMethod->isVariadic()) {
+ array_pop($parameters);
+ }
- foreach ($reflectionMethod->getParameters() as $index => $parameter) {
+ foreach ($parameters as $index => $parameter) {
if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) {
continue;
}
if (!$isConstructor && $parameter->isOptional() && !array_key_exists($index, $arguments)) {
break;
}
- if (method_exists($parameter, 'isVariadic') && $parameter->isVariadic()) {
- continue;
- }
$type = ProxyHelper::getTypeHint($reflectionMethod, $parameter, true);
if (!$type) {
+ if (isset($arguments[$index])) {
+ continue;
+ }
+
// no default value? Then fail
if (!$parameter->isOptional()) {
throw new RuntimeException(sprintf('Cannot autowire service "%s": argument $%s of method %s() must have a type-hint or be given a value explicitly.', $this->currentId, $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
}
- if (!array_key_exists($index, $arguments)) {
- // specifically pass the default value
- $arguments[$index] = $parameter->getDefaultValue();
- }
+ // specifically pass the default value
+ $arguments[$index] = $parameter->getDefaultValue();
continue;
}
- if ($value = $this->getAutowiredReference($type)) {
- $this->usedTypes[$type] = $this->currentId;
- } else {
+ if (!$value = $this->getAutowiredReference($type)) {
$failureMessage = $this->createTypeNotFoundMessage($type, sprintf('argument $%s of method %s()', $parameter->name, $class !== $this->currentId ? $class.'::'.$method : $method));
if ($parameter->isDefaultValueAvailable()) {
@@ -296,6 +284,16 @@ class AutowirePass extends AbstractRecursivePass
$arguments[$index] = $value;
}
+ if ($parameters && !isset($arguments[++$index])) {
+ while (0 <= --$index) {
+ $parameter = $parameters[$index];
+ if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) {
+ break;
+ }
+ unset($arguments[$index]);
+ }
+ }
+
// it's possible index 1 was set, then index 0, then 2, etc
// make sure that we re-order so they're injected as expected
ksort($arguments);
@@ -305,6 +303,8 @@ class AutowirePass extends AbstractRecursivePass
/**
* @return Reference|null A reference to the service matching the given type, if any
+ *
+ * @throws RuntimeException
*/
private function getAutowiredReference($type, $autoRegister = true)
{
@@ -316,6 +316,10 @@ class AutowirePass extends AbstractRecursivePass
return;
}
+ if (isset($this->autowired[$type])) {
+ return $this->autowired[$type] ? new Reference($this->autowired[$type]) : null;
+ }
+
if (null === $this->types) {
$this->populateAvailableTypes();
}
@@ -326,8 +330,14 @@ class AutowirePass extends AbstractRecursivePass
return new Reference($this->types[$type]);
}
- if ($autoRegister && $class = $this->container->getReflectionClass($type, true)) {
- return $this->createAutowiredDefinition($class);
+ if (isset($this->ambiguousServiceTypes[$type])) {
+ $classOrInterface = class_exists($type, false) ? 'class' : 'interface';
+
+ throw new RuntimeException(sprintf('Cannot autowire service "%s": multiple candidate services exist for %s "%s".%s', $this->currentId, $classOrInterface, $type, $this->createTypeAlternatives($type)));
+ }
+
+ if ($autoRegister) {
+ return $this->createAutowiredDefinition($type);
}
}
@@ -412,45 +422,28 @@ class AutowirePass extends AbstractRecursivePass
/**
* Registers a definition for the type if possible or throws an exception.
*
- * @param \ReflectionClass $typeHint
+ * @param string $type
*
* @return Reference|null A reference to the registered definition
- *
- * @throws RuntimeException
*/
- private function createAutowiredDefinition(\ReflectionClass $typeHint)
+ private function createAutowiredDefinition($type)
{
- if (isset($this->ambiguousServiceTypes[$type = $typeHint->name])) {
- $classOrInterface = class_exists($type) ? 'class' : 'interface';
-
- throw new RuntimeException(sprintf('Cannot autowire service "%s": multiple candidate services exist for %s "%s".%s', $this->currentId, $classOrInterface, $type, $this->createTypeAlternatives($type)));
- }
-
- if (!$typeHint->isInstantiable()) {
- $this->container->log($this, sprintf('Type "%s" is not instantiable thus cannot be auto-registered for service "%s".', $type, $this->currentId));
-
+ if (!($typeHint = $this->container->getReflectionClass($type, true)) || !$typeHint->isInstantiable()) {
return;
}
- $ambiguousServiceTypes = $this->ambiguousServiceTypes;
$currentDefinition = $this->currentDefinition;
- $definitions = $this->container->getDefinitions();
$currentId = $this->currentId;
- $this->currentId = $argumentId = sprintf('autowired.%s', $type);
+ $this->currentId = $this->autowired[$type] = $argumentId = sprintf('autowired.%s', $type);
$this->currentDefinition = $argumentDefinition = new Definition($type);
$argumentDefinition->setPublic(false);
$argumentDefinition->setAutowired(true);
- $this->populateAvailableType($argumentId, $argumentDefinition);
-
try {
$this->processValue($argumentDefinition, true);
$this->container->setDefinition($argumentId, $argumentDefinition);
} catch (RuntimeException $e) {
- // revert any changes done to our internal state
- unset($this->types[$type]);
- $this->ambiguousServiceTypes = $ambiguousServiceTypes;
- $this->container->setDefinitions($definitions);
+ $this->autowired[$type] = false;
$this->container->log($this, $e->getMessage());
return;
diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
index 9d2876a595..63b2bc8662 100644
--- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
+++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php
@@ -81,7 +81,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
*/
private $compiler;
- private $trackResources = true;
+ private $trackResources;
/**
* @var InstantiatorInterface|null
@@ -122,6 +122,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
{
parent::__construct($parameterBag);
+ $this->trackResources = interface_exists('Symfony\Component\Config\Resource\ResourceInterface');
$this->setDefinition('service_container', (new Definition(ContainerInterface::class))->setSynthetic(true));
$this->setAlias(PsrContainerInterface::class, new Alias('service_container', false));
$this->setAlias(ContainerInterface::class, new Alias('service_container', false));
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
index c5dbf101ec..6dbc7c87e5 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php
@@ -444,10 +444,6 @@ class AutowirePassTest extends TestCase
array(
new Reference('a'),
new Reference('lille'),
- // third arg shouldn't *need* to be passed
- // but that's hard to "pull of" with autowiring, so
- // this assumes passing the default val is ok
- 'some_val',
),
$definition->getArguments()
);
@@ -618,30 +614,21 @@ class AutowirePassTest extends TestCase
}
/**
- * @dataProvider provideAutodiscoveredAutowiringOrder
- *
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
- * @expectedExceptionMEssage Unable to autowire argument of type "Symfony\Component\DependencyInjection\Tests\Compiler\CollisionInterface" for service "a". Multiple services exist for this interface: autowired.Symfony\Component\DependencyInjection\Tests\Compiler\CollisionA, autowired.Symfony\Component\DependencyInjection\Tests\Compiler\CollisionB.
+ * @expectedExceptionMessage Service "a" can use either autowiring or a factory, not both.
*/
- public function testAutodiscoveredAutowiringOrder($class)
+ public function testWithFactory()
{
$container = new ContainerBuilder();
- $container->register('a', __NAMESPACE__.'\\'.$class)
+ $container->register('a', __NAMESPACE__.'\A')
+ ->setFactory('foo')
->setAutowired(true);
$pass = new AutowirePass();
$pass->process($container);
}
- public function provideAutodiscoveredAutowiringOrder()
- {
- return array(
- array('CannotBeAutowiredForwardOrder'),
- array('CannotBeAutowiredReverseOrder'),
- );
- }
-
/**
* @dataProvider provideNotWireableCalls
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
@@ -680,19 +667,6 @@ class AutowirePassTest extends TestCase
);
}
- public function testAutoregisterRestoresStateOnFailure()
- {
- $container = new ContainerBuilder();
-
- $container->register('e', E::class)
- ->setAutowired(true);
-
- $pass = new AutowirePass();
- $pass->process($container);
-
- $this->assertSame(array('service_container', 'e'), array_keys($container->getDefinitions()));
- }
-
/**
* @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException
* @expectedExceptionMessage Cannot autowire service "j": multiple candidate services exist for class "Symfony\Component\DependencyInjection\Tests\Compiler\I". This type-hint could be aliased to one of these existing services: "f", "i"; or be updated to "Symfony\Component\DependencyInjection\Tests\Compiler\IInterface".
diff --git a/src/Symfony/Component/EventDispatcher/EventDispatcher.php b/src/Symfony/Component/EventDispatcher/EventDispatcher.php
index 2c977f4731..01bfc0066e 100644
--- a/src/Symfony/Component/EventDispatcher/EventDispatcher.php
+++ b/src/Symfony/Component/EventDispatcher/EventDispatcher.php
@@ -93,7 +93,7 @@ class EventDispatcher implements EventDispatcherInterface
*/
public function hasListeners($eventName = null)
{
- return (bool) count($this->getListeners($eventName));
+ return (bool) $this->getListeners($eventName);
}
/**
diff --git a/src/Symfony/Component/ExpressionLanguage/Lexer.php b/src/Symfony/Component/ExpressionLanguage/Lexer.php
index 26bb51d42e..8c10b72d86 100644
--- a/src/Symfony/Component/ExpressionLanguage/Lexer.php
+++ b/src/Symfony/Component/ExpressionLanguage/Lexer.php
@@ -59,12 +59,12 @@ class Lexer
} elseif (false !== strpos(')]}', $expression[$cursor])) {
// closing bracket
if (empty($brackets)) {
- throw new SyntaxError(sprintf('Unexpected "%s"', $expression[$cursor]), $cursor);
+ throw new SyntaxError(sprintf('Unexpected "%s"', $expression[$cursor]), $cursor, $expression);
}
list($expect, $cur) = array_pop($brackets);
if ($expression[$cursor] != strtr($expect, '([{', ')]}')) {
- throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur);
+ throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur, $expression);
}
$tokens[] = new Token(Token::PUNCTUATION_TYPE, $expression[$cursor], $cursor + 1);
@@ -87,7 +87,7 @@ class Lexer
$cursor += strlen($match[0]);
} else {
// unlexable
- throw new SyntaxError(sprintf('Unexpected character "%s"', $expression[$cursor]), $cursor);
+ throw new SyntaxError(sprintf('Unexpected character "%s"', $expression[$cursor]), $cursor, $expression);
}
}
@@ -95,9 +95,9 @@ class Lexer
if (!empty($brackets)) {
list($expect, $cur) = array_pop($brackets);
- throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur);
+ throw new SyntaxError(sprintf('Unclosed "%s"', $expect), $cur, $expression);
}
- return new TokenStream($tokens);
+ return new TokenStream($tokens, $expression);
}
}
diff --git a/src/Symfony/Component/ExpressionLanguage/Parser.php b/src/Symfony/Component/ExpressionLanguage/Parser.php
index f8900bfb12..c75972447d 100644
--- a/src/Symfony/Component/ExpressionLanguage/Parser.php
+++ b/src/Symfony/Component/ExpressionLanguage/Parser.php
@@ -99,7 +99,7 @@ class Parser
$node = $this->parseExpression();
if (!$stream->isEOF()) {
- throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $stream->current->type, $stream->current->value), $stream->current->cursor);
+ throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $stream->current->type, $stream->current->value), $stream->current->cursor, $stream->getExpression());
}
return $node;
@@ -195,13 +195,13 @@ class Parser
default:
if ('(' === $this->stream->current->value) {
if (false === isset($this->functions[$token->value])) {
- throw new SyntaxError(sprintf('The function "%s" does not exist', $token->value), $token->cursor);
+ throw new SyntaxError(sprintf('The function "%s" does not exist', $token->value), $token->cursor, $this->stream->getExpression());
}
$node = new Node\FunctionNode($token->value, $this->parseArguments());
} else {
if (!in_array($token->value, $this->names, true)) {
- throw new SyntaxError(sprintf('Variable "%s" is not valid', $token->value), $token->cursor);
+ throw new SyntaxError(sprintf('Variable "%s" is not valid', $token->value), $token->cursor, $this->stream->getExpression());
}
// is the name used in the compiled code different
@@ -227,7 +227,7 @@ class Parser
} elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) {
$node = $this->parseHashExpression();
} else {
- throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $token->type, $token->value), $token->cursor);
+ throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s"', $token->type, $token->value), $token->cursor, $this->stream->getExpression());
}
}
@@ -289,7 +289,7 @@ class Parser
} else {
$current = $this->stream->current;
- throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', $current->type, $current->value), $current->cursor);
+ throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s"', $current->type, $current->value), $current->cursor, $this->stream->getExpression());
}
$this->stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
@@ -327,7 +327,7 @@ class Parser
// As a result, if $token is NOT an operator OR $token->value is NOT a valid property or method name, an exception shall be thrown.
($token->type !== Token::OPERATOR_TYPE || !preg_match('/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A', $token->value))
) {
- throw new SyntaxError('Expected name', $token->cursor);
+ throw new SyntaxError('Expected name', $token->cursor, $this->stream->getExpression());
}
$arg = new Node\ConstantNode($token->value, true);
diff --git a/src/Symfony/Component/ExpressionLanguage/SyntaxError.php b/src/Symfony/Component/ExpressionLanguage/SyntaxError.php
index d149c00768..9373e9980b 100644
--- a/src/Symfony/Component/ExpressionLanguage/SyntaxError.php
+++ b/src/Symfony/Component/ExpressionLanguage/SyntaxError.php
@@ -13,8 +13,14 @@ namespace Symfony\Component\ExpressionLanguage;
class SyntaxError extends \LogicException
{
- public function __construct($message, $cursor = 0)
+ public function __construct($message, $cursor = 0, $expression = '')
{
- parent::__construct(sprintf('%s around position %d.', $message, $cursor));
+ $message = sprintf('%s around position %d', $message, $cursor);
+ if ($expression) {
+ $message = sprintf('%s for expression `%s`', $message, $expression);
+ }
+ $message .= '.';
+
+ parent::__construct($message);
}
}
diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php
index 4292c22359..87c16f707b 100644
--- a/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php
+++ b/src/Symfony/Component/ExpressionLanguage/Tests/LexerTest.php
@@ -18,14 +18,43 @@ use Symfony\Component\ExpressionLanguage\TokenStream;
class LexerTest extends TestCase
{
+ /**
+ * @var Lexer
+ */
+ private $lexer;
+
+ protected function setUp()
+ {
+ $this->lexer = new Lexer();
+ }
+
/**
* @dataProvider getTokenizeData
*/
public function testTokenize($tokens, $expression)
{
$tokens[] = new Token('end of expression', null, strlen($expression) + 1);
- $lexer = new Lexer();
- $this->assertEquals(new TokenStream($tokens), $lexer->tokenize($expression));
+ $this->assertEquals(new TokenStream($tokens, $expression), $this->lexer->tokenize($expression));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
+ * @expectedExceptionMessage Unexpected character "'" around position 33 for expression `service(faulty.expression.example').dummyMethod()`.
+ */
+ public function testTokenizeThrowsErrorWithMessage()
+ {
+ $expression = "service(faulty.expression.example').dummyMethod()";
+ $this->lexer->tokenize($expression);
+ }
+
+ /**
+ * @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
+ * @expectedExceptionMessage Unclosed "(" around position 7 for expression `service(unclosed.expression.dummyMethod()`.
+ */
+ public function testTokenizeThrowsErrorOnUnclosedBrace()
+ {
+ $expression = 'service(unclosed.expression.dummyMethod()';
+ $this->lexer->tokenize($expression);
}
public function getTokenizeData()
diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php
index b8b954afe0..13b80cd64f 100644
--- a/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php
+++ b/src/Symfony/Component/ExpressionLanguage/Tests/ParserTest.php
@@ -20,7 +20,7 @@ class ParserTest extends TestCase
{
/**
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
- * @expectedExceptionMessage Variable "foo" is not valid around position 1.
+ * @expectedExceptionMessage Variable "foo" is not valid around position 1 for expression `foo`.
*/
public function testParseWithInvalidName()
{
@@ -31,7 +31,7 @@ class ParserTest extends TestCase
/**
* @expectedException \Symfony\Component\ExpressionLanguage\SyntaxError
- * @expectedExceptionMessage Variable "foo" is not valid around position 1.
+ * @expectedExceptionMessage Variable "foo" is not valid around position 1 for expression `foo`.
*/
public function testParseWithZeroInNames()
{
diff --git a/src/Symfony/Component/ExpressionLanguage/TokenStream.php b/src/Symfony/Component/ExpressionLanguage/TokenStream.php
index 6c4af745b2..3c22fc1d46 100644
--- a/src/Symfony/Component/ExpressionLanguage/TokenStream.php
+++ b/src/Symfony/Component/ExpressionLanguage/TokenStream.php
@@ -22,16 +22,19 @@ class TokenStream
private $tokens;
private $position = 0;
+ private $expression;
/**
* Constructor.
*
- * @param array $tokens An array of tokens
+ * @param array $tokens An array of tokens
+ * @param string $expression
*/
- public function __construct(array $tokens)
+ public function __construct(array $tokens, $expression = '')
{
$this->tokens = $tokens;
$this->current = $tokens[0];
+ $this->expression = $expression;
}
/**
@@ -50,7 +53,7 @@ class TokenStream
public function next()
{
if (!isset($this->tokens[$this->position])) {
- throw new SyntaxError('Unexpected end of expression', $this->current->cursor);
+ throw new SyntaxError('Unexpected end of expression', $this->current->cursor, $this->expression);
}
++$this->position;
@@ -69,7 +72,7 @@ class TokenStream
{
$token = $this->current;
if (!$token->test($type, $value)) {
- throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)', $message ? $message.'. ' : '', $token->type, $token->value, $type, $value ? sprintf(' with value "%s"', $value) : ''), $token->cursor);
+ throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)', $message ? $message.'. ' : '', $token->type, $token->value, $type, $value ? sprintf(' with value "%s"', $value) : ''), $token->cursor, $this->expression);
}
$this->next();
}
@@ -83,4 +86,14 @@ class TokenStream
{
return $this->current->type === Token::EOF_TYPE;
}
+
+ /**
+ * @internal
+ *
+ * @return string
+ */
+ public function getExpression()
+ {
+ return $this->expression;
+ }
}
diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php
index 88531feaa2..7fc191a054 100644
--- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php
+++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php
@@ -120,7 +120,7 @@ class PercentToLocalizedStringTransformer implements DataTransformerInterface
$formatter = $this->getNumberFormatter();
// replace normal spaces so that the formatter can read them
- $value = $formatter->parse(str_replace(' ', ' ', $value));
+ $value = $formatter->parse(str_replace(' ', "\xc2\xa0", $value));
if (intl_is_failure($formatter->getErrorCode())) {
throw new TransformationFailedException($formatter->getErrorMessage());
diff --git a/src/Symfony/Component/Form/Form.php b/src/Symfony/Component/Form/Form.php
index a04a17c908..cdbb0fe171 100644
--- a/src/Symfony/Component/Form/Form.php
+++ b/src/Symfony/Component/Form/Form.php
@@ -407,6 +407,10 @@ class Form implements \IteratorAggregate, FormInterface
}
if (!$this->defaultDataSet) {
+ if ($this->lockSetData) {
+ throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call getData() if the form data has not already been set. You should call getData() on the FormEvent object instead.');
+ }
+
$this->setData($this->config->getData());
}
@@ -427,6 +431,10 @@ class Form implements \IteratorAggregate, FormInterface
}
if (!$this->defaultDataSet) {
+ if ($this->lockSetData) {
+ throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call getNormData() if the form data has not already been set.');
+ }
+
$this->setData($this->config->getData());
}
@@ -447,6 +455,10 @@ class Form implements \IteratorAggregate, FormInterface
}
if (!$this->defaultDataSet) {
+ if ($this->lockSetData) {
+ throw new RuntimeException('A cycle was detected. Listeners to the PRE_SET_DATA event must not call getViewData() if the form data has not already been set.');
+ }
+
$this->setData($this->config->getData());
}
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 c1f9509678..5a47594154 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/DateTypeTest.php
@@ -598,7 +598,7 @@ class DateTypeTest extends BaseTypeTest
));
$form->submit(array(
- 'day' => '0',
+ 'day' => '1',
'month' => '6',
'year' => '2010',
));
diff --git a/src/Symfony/Component/Form/Tests/SimpleFormTest.php b/src/Symfony/Component/Form/Tests/SimpleFormTest.php
index 689f2e8f77..d80ec68b8e 100644
--- a/src/Symfony/Component/Form/Tests/SimpleFormTest.php
+++ b/src/Symfony/Component/Form/Tests/SimpleFormTest.php
@@ -896,6 +896,7 @@ class SimpleFormTest extends AbstractFormTest
/**
* @expectedException \Symfony\Component\Form\Exception\RuntimeException
+ * @expectedExceptionMessage A cycle was detected. Listeners to the PRE_SET_DATA event must not call setData(). You should call setData() on the FormEvent object instead.
*/
public function testSetDataCannotInvokeItself()
{
@@ -1062,6 +1063,51 @@ class SimpleFormTest extends AbstractFormTest
$child->initialize();
}
+ /**
+ * @expectedException \Symfony\Component\Form\Exception\RuntimeException
+ * @expectedExceptionMessage A cycle was detected. Listeners to the PRE_SET_DATA event must not call getData() if the form data has not already been set. You should call getData() on the FormEvent object instead.
+ */
+ public function testCannotCallGetDataInPreSetDataListenerIfDataHasNotAlreadyBeenSet()
+ {
+ $config = new FormConfigBuilder('name', 'stdClass', $this->dispatcher);
+ $config->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
+ $event->getForm()->getData();
+ });
+ $form = new Form($config);
+
+ $form->setData('foo');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Form\Exception\RuntimeException
+ * @expectedExceptionMessage A cycle was detected. Listeners to the PRE_SET_DATA event must not call getNormData() if the form data has not already been set.
+ */
+ public function testCannotCallGetNormDataInPreSetDataListener()
+ {
+ $config = new FormConfigBuilder('name', 'stdClass', $this->dispatcher);
+ $config->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
+ $event->getForm()->getNormData();
+ });
+ $form = new Form($config);
+
+ $form->setData('foo');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Form\Exception\RuntimeException
+ * @expectedExceptionMessage A cycle was detected. Listeners to the PRE_SET_DATA event must not call getViewData() if the form data has not already been set.
+ */
+ public function testCannotCallGetViewDataInPreSetDataListener()
+ {
+ $config = new FormConfigBuilder('name', 'stdClass', $this->dispatcher);
+ $config->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
+ $event->getForm()->getViewData();
+ });
+ $form = new Form($config);
+
+ $form->setData('foo');
+ }
+
protected function createForm()
{
return $this->getBuilder()->getForm();
diff --git a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php
index a059d3c507..e90579efbb 100644
--- a/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php
+++ b/src/Symfony/Component/PropertyInfo/Extractor/PhpDocExtractor.php
@@ -213,7 +213,7 @@ class PhpDocExtractor implements PropertyDescriptionExtractorInterface, Property
* @param string $ucFirstProperty
* @param int $type
*
- * @return array
+ * @return array|null
*/
private function getDocBlockFromMethod($class, $ucFirstProperty, $type)
{
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php
index 8aa508e3c2..d0eb3eed06 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractors/PhpDocExtractorTest.php
@@ -68,6 +68,7 @@ class PhpDocExtractorTest extends TestCase
array('d', array(new Type(Type::BUILTIN_TYPE_BOOL)), null, null),
array('e', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))), null, null),
array('f', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null),
+ array('g', array(new Type(Type::BUILTIN_TYPE_ARRAY, true, null, true)), 'Nullable array.', null),
array('donotexist', null, null, null),
array('staticGetter', null, null, null),
array('staticSetter', null, null, null),
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php
index f8a5e45360..77230fba05 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Extractors/ReflectionExtractorTest.php
@@ -39,6 +39,7 @@ class ReflectionExtractorTest extends TestCase
'parent',
'collection',
'B',
+ 'g',
'foo',
'foo2',
'foo3',
diff --git a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php
index 96cb87db4a..12065b18b6 100644
--- a/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php
+++ b/src/Symfony/Component/PropertyInfo/Tests/Fixtures/Dummy.php
@@ -51,6 +51,13 @@ class Dummy extends ParentDummy
*/
public $B;
+ /**
+ * Nullable array.
+ *
+ * @var array|null
+ */
+ public $g;
+
public static function getStatic()
{
}
diff --git a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
index b374a5dcba..bc14cd8b69 100644
--- a/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
+++ b/src/Symfony/Component/PropertyInfo/Util/PhpDocTypeHelper.php
@@ -101,10 +101,10 @@ final class PhpDocTypeHelper
$collectionValueType = null;
} else {
$collectionKeyType = new Type(Type::BUILTIN_TYPE_INT);
- $collectionValueType = new Type($phpType, false, $class);
+ $collectionValueType = new Type($phpType, $nullable, $class);
}
- return new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, $collectionKeyType, $collectionValueType);
+ return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType);
}
return new Type($phpType, $nullable, $class);