diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php
index 8746998468..d09de2d615 100644
--- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php
+++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php
@@ -11,6 +11,7 @@
namespace Symfony\Bundle\TwigBundle\DependencyInjection\Compiler;
+use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
@@ -73,7 +74,7 @@ class ExtensionPass implements CompilerPassInterface
$loader->addTag('twig.loader');
$loader->setMethodCalls($container->getDefinition('twig.loader.filesystem')->getMethodCalls());
- $container->setDefinition('twig.loader.filesystem', $loader);
+ $container->setAlias('twig.loader.filesystem', new Alias('twig.loader.native_filesystem', false));
}
if ($container->has('assets.packages')) {
diff --git a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php
index 0d722ed5aa..eb02c89dfa 100644
--- a/src/Symfony/Component/Console/Descriptor/TextDescriptor.php
+++ b/src/Symfony/Component/Console/Descriptor/TextDescriptor.php
@@ -38,13 +38,13 @@ class TextDescriptor extends Descriptor
}
$totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName());
- $spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
+ $spacingWidth = $totalWidth - strlen($argument->getName());
- $this->writeText(sprintf(' %s%s%s%s',
+ $this->writeText(sprintf(' %s %s%s%s',
$argument->getName(),
str_repeat(' ', $spacingWidth),
- // + 17 = 2 spaces + + + 2 spaces
- preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 17), $argument->getDescription()),
+ // + 4 = 2 spaces before , 2 spaces after
+ preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
$default
), $options);
}
@@ -75,13 +75,13 @@ class TextDescriptor extends Descriptor
sprintf('--%s%s', $option->getName(), $value)
);
- $spacingWidth = $totalWidth - strlen($synopsis) + 2;
+ $spacingWidth = $totalWidth - strlen($synopsis);
- $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
- preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 17), $option->getDescription()),
+ // + 4 = 2 spaces before , 2 spaces after
+ preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
$default,
$option->isArray() ? ' (multiple values allowed)' : ''
), $options);
diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_4.txt b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_4.txt
index aa74e8ceb2..fc7d669a11 100644
--- a/src/Symfony/Component/Console/Tests/Fixtures/input_argument_4.txt
+++ b/src/Symfony/Component/Console/Tests/Fixtures/input_argument_4.txt
@@ -1,2 +1,2 @@
argument_name multiline
- argument description
+ argument description
diff --git a/src/Symfony/Component/Console/Tests/Fixtures/input_option_5.txt b/src/Symfony/Component/Console/Tests/Fixtures/input_option_5.txt
index 4368883cc7..9563b4cab1 100644
--- a/src/Symfony/Component/Console/Tests/Fixtures/input_option_5.txt
+++ b/src/Symfony/Component/Console/Tests/Fixtures/input_option_5.txt
@@ -1,2 +1,2 @@
-o, --option_name=OPTION_NAME multiline
- option description
+ option description
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php b/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php
index d7570ddc2c..156bcc0c3a 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/CheckCircularReferencesPass.php
@@ -60,15 +60,19 @@ class CheckCircularReferencesPass implements CompilerPassInterface
$id = $node->getId();
if (empty($this->checkedNodes[$id])) {
- $searchKey = array_search($id, $this->currentPath);
- $this->currentPath[] = $id;
- if (false !== $searchKey) {
- throw new ServiceCircularReferenceException($id, array_slice($this->currentPath, $searchKey));
+ // don't check circular dependencies for lazy services
+ if (!$node->getValue() || !$node->getValue()->isLazy()) {
+ $searchKey = array_search($id, $this->currentPath);
+ $this->currentPath[] = $id;
+
+ if (false !== $searchKey) {
+ throw new ServiceCircularReferenceException($id, array_slice($this->currentPath, $searchKey));
+ }
+
+ $this->checkOutEdges($node->getOutEdges());
}
- $this->checkOutEdges($node->getOutEdges());
-
$this->checkedNodes[$id] = true;
array_pop($this->currentPath);
}
diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
index 96d5b4dfd3..2ef29aa3cf 100644
--- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
+++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php
@@ -1313,6 +1313,13 @@ EOF;
$visited[$argumentId] = true;
$service = $this->container->getDefinition($argumentId);
+
+ // if the proxy manager is enabled, disable searching for references in lazy services,
+ // as these services will be instantiated lazily and don't have direct related references.
+ if ($service->isLazy() && !$this->getProxyDumper() instanceof NullDumper) {
+ continue;
+ }
+
$arguments = array_merge($service->getMethodCalls(), $service->getArguments(), $service->getProperties());
if ($this->hasReference($id, $arguments, $deep, $visited)) {
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
index 5c49f1755f..18fd740a9a 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\DependencyInjection\Tests\Dumper;
+use DummyProxyDumper;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
@@ -19,6 +20,8 @@ use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Variable;
use Symfony\Component\ExpressionLanguage\Expression;
+require_once __DIR__.'/../Fixtures/includes/classes.php';
+
class PhpDumperTest extends \PHPUnit_Framework_TestCase
{
protected static $fixturesPath;
@@ -294,4 +297,52 @@ class PhpDumperTest extends \PHPUnit_Framework_TestCase
$container = new \Symfony_DI_PhpDumper_Test_Properties_Before_Method_Calls();
$this->assertTrue($container->get('bar')->callPassed(), '->dump() initializes properties before method calls');
}
+
+ public function testCircularReferenceAllowanceForLazyServices()
+ {
+ $container = new ContainerBuilder();
+ $container->register('foo', 'stdClass')->addArgument(new Reference('bar'));
+ $container->register('bar', 'stdClass')->setLazy(true)->addArgument(new Reference('foo'));
+ $container->compile();
+
+ $dumper = new PhpDumper($container);
+ $dumper->dump();
+ }
+
+ public function testCircularReferenceAllowanceForInlinedDefinitionsForLazyServices()
+ {
+ /*
+ * test graph:
+ * [connection] -> [event_manager] --> [entity_manager](lazy)
+ * |
+ * --(call)- addEventListener ("@lazy_service")
+ *
+ * [lazy_service](lazy) -> [entity_manager](lazy)
+ *
+ */
+
+ $container = new ContainerBuilder();
+
+ $eventManagerDefinition = new Definition('stdClass');
+
+ $connectionDefinition = $container->register('connection', 'stdClass');
+ $connectionDefinition->addArgument($eventManagerDefinition);
+
+ $container->register('entity_manager', 'stdClass')
+ ->setLazy(true)
+ ->addArgument(new Reference('connection'));
+
+ $lazyServiceDefinition = $container->register('lazy_service', 'stdClass');
+ $lazyServiceDefinition->setLazy(true);
+ $lazyServiceDefinition->addArgument(new Reference('entity_manager'));
+
+ $eventManagerDefinition->addMethodCall('addEventListener', array(new Reference('lazy_service')));
+
+ $container->compile();
+
+ $dumper = new PhpDumper($container);
+
+ $dumper->setProxyDumper(new DummyProxyDumper());
+ $dumper->dump();
+ }
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php
index 48b687c1f4..0ecdea3fbf 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php
@@ -1,5 +1,8 @@
configure();
@@ -76,3 +79,21 @@ class MethodCallClass
return $this->callPassed;
}
}
+
+class DummyProxyDumper implements ProxyDumper
+{
+ public function isProxyCandidate(Definition $definition)
+ {
+ return false;
+ }
+
+ public function getProxyFactoryCode(Definition $definition, $id)
+ {
+ return '';
+ }
+
+ public function getProxyCode(Definition $definition)
+ {
+ return '';
+ }
+}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
index 25ebbd35d8..526bfdb6b9 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php
@@ -39,6 +39,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class ChoiceType extends AbstractType
{
+ /**
+ * @internal To be removed in 3.0
+ */
+ const DEPRECATED_EMPTY_VALUE = '__deprecated_empty_value__';
+
/**
* Caches created choice lists.
*
@@ -344,7 +349,7 @@ class ChoiceType extends AbstractType
};
$placeholderNormalizer = function (Options $options, $placeholder) use ($that) {
- if (!is_object($options['empty_value']) || !$options['empty_value'] instanceof \Exception) {
+ if ($that::DEPRECATED_EMPTY_VALUE !== $options['empty_value']) {
@trigger_error(sprintf('The form option "empty_value" of the "%s" form type (%s) is deprecated since version 2.6 and will be removed in 3.0. Use "placeholder" instead.', $that->getName(), __CLASS__), E_USER_DEPRECATED);
if (null === $placeholder || '' === $placeholder) {
@@ -396,7 +401,7 @@ class ChoiceType extends AbstractType
'preferred_choices' => array(),
'group_by' => null,
'empty_data' => $emptyData,
- 'empty_value' => new \Exception(), // deprecated
+ 'empty_value' => self::DEPRECATED_EMPTY_VALUE,
'placeholder' => $placeholder,
'error_bubbling' => false,
'compound' => $compound,
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
index c48fde3510..209ece5cae 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/DateType.php
@@ -191,7 +191,7 @@ class DateType extends AbstractType
};
$placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) {
- if (!is_object($options['empty_value']) || !$options['empty_value'] instanceof \Exception) {
+ if (ChoiceType::DEPRECATED_EMPTY_VALUE !== $options['empty_value']) {
@trigger_error('The form option "empty_value" is deprecated since version 2.6 and will be removed in 3.0. Use "placeholder" instead.', E_USER_DEPRECATED);
$placeholder = $options['empty_value'];
@@ -243,7 +243,7 @@ class DateType extends AbstractType
'format' => $format,
'model_timezone' => null,
'view_timezone' => null,
- 'empty_value' => new \Exception(), // deprecated
+ 'empty_value' => ChoiceType::DEPRECATED_EMPTY_VALUE,
'placeholder' => $placeholder,
'html5' => true,
// Don't modify \DateTime classes by reference, we treat
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
index 6c67b8dc4b..40da512b30 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php
@@ -12,12 +12,37 @@
namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\FormEvent;
+use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
+use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FileType extends AbstractType
{
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(FormBuilderInterface $builder, array $options)
+ {
+ if ($options['multiple']) {
+ $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
+ $form = $event->getForm();
+ $data = $event->getData();
+
+ // submitted data for an input file (not required) without choosing any file
+ if (array(null) === $data) {
+ $emptyData = $form->getConfig()->getEmptyData();
+
+ $data = is_callable($emptyData) ? call_user_func($emptyData, $form, $data) : $emptyData;
+ $event->setData($data);
+ }
+ });
+ }
+ }
+
/**
* {@inheritdoc}
*/
@@ -39,9 +64,7 @@ class FileType extends AbstractType
*/
public function finishView(FormView $view, FormInterface $form, array $options)
{
- $view
- ->vars['multipart'] = true
- ;
+ $view->vars['multipart'] = true;
}
/**
@@ -49,10 +72,18 @@ class FileType extends AbstractType
*/
public function configureOptions(OptionsResolver $resolver)
{
+ $dataClass = function (Options $options) {
+ return $options['multiple'] ? null : 'Symfony\Component\HttpFoundation\File\File';
+ };
+
+ $emptyData = function (Options $options) {
+ return $options['multiple'] ? array() : null;
+ };
+
$resolver->setDefaults(array(
'compound' => false,
- 'data_class' => 'Symfony\Component\HttpFoundation\File\File',
- 'empty_data' => null,
+ 'data_class' => $dataClass,
+ 'empty_data' => $emptyData,
'multiple' => false,
));
}
diff --git a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
index 81edfd6c42..8aae516a5e 100644
--- a/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
+++ b/src/Symfony/Component/Form/Extension/Core/Type/TimeType.php
@@ -192,7 +192,7 @@ class TimeType extends AbstractType
};
$placeholderNormalizer = function (Options $options, $placeholder) use ($placeholderDefault) {
- if (!is_object($options['empty_value']) || !$options['empty_value'] instanceof \Exception) {
+ if (ChoiceType::DEPRECATED_EMPTY_VALUE !== $options['empty_value']) {
@trigger_error('The form option "empty_value" is deprecated since version 2.6 and will be removed in 3.0. Use "placeholder" instead.', E_USER_DEPRECATED);
$placeholder = $options['empty_value'];
@@ -241,7 +241,7 @@ class TimeType extends AbstractType
'with_seconds' => false,
'model_timezone' => null,
'view_timezone' => null,
- 'empty_value' => new \Exception(), // deprecated
+ 'empty_value' => ChoiceType::DEPRECATED_EMPTY_VALUE,
'placeholder' => $placeholder,
'html5' => true,
// Don't modify \DateTime classes by reference, we treat
diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php
index eebb6ad583..cbeeb468c1 100644
--- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php
+++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/FileTypeTest.php
@@ -54,6 +54,33 @@ class FileTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
$this->assertNull($form->getData());
}
+ public function testSubmitEmptyMultiple()
+ {
+ $form = $this->factory->createBuilder('file', null, array(
+ 'multiple' => true,
+ ))->getForm();
+
+ // submitted data when an input file is uploaded without choosing any file
+ $form->submit(array(null));
+
+ $this->assertSame(array(), $form->getData());
+ }
+
+ public function testSetDataMultiple()
+ {
+ $form = $this->factory->createBuilder('file', null, array(
+ 'multiple' => true,
+ ))->getForm();
+
+ $data = array(
+ $this->createUploadedFileMock('abcdef', 'first.jpg', true),
+ $this->createUploadedFileMock('zyxwvu', 'second.jpg', true),
+ );
+
+ $form->setData($data);
+ $this->assertSame($data, $form->getData());
+ }
+
public function testSubmitMultiple()
{
$form = $this->factory->createBuilder('Symfony\Component\Form\Extension\Core\Type\FileType', null, array(
diff --git a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php
index c76b57bb6a..a1cff53535 100644
--- a/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php
+++ b/src/Symfony/Component/HttpKernel/Controller/ControllerResolver.php
@@ -36,6 +36,13 @@ class ControllerResolver implements ControllerResolverInterface
*/
private $supportsVariadic;
+ /**
+ * If scalar types exists.
+ *
+ * @var bool
+ */
+ private $supportsScalarTypes;
+
/**
* Constructor.
*
@@ -46,6 +53,7 @@ class ControllerResolver implements ControllerResolverInterface
$this->logger = $logger;
$this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic');
+ $this->supportsScalarTypes = method_exists('ReflectionParameter', 'getType');
}
/**
@@ -132,7 +140,7 @@ class ControllerResolver implements ControllerResolverInterface
$arguments[] = $request;
} elseif ($param->isDefaultValueAvailable()) {
$arguments[] = $param->getDefaultValue();
- } elseif ($param->allowsNull()) {
+ } elseif ($this->supportsScalarTypes && $param->hasType() && $param->allowsNull()) {
$arguments[] = null;
} else {
if (is_array($controller)) {
diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php
index 9aa7e20a9a..f3b0ac025d 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ControllerResolverTest.php
@@ -223,6 +223,19 @@ class ControllerResolverTest extends \PHPUnit_Framework_TestCase
$mock->getController($request);
}
+ /**
+ * @expectedException \RuntimeException
+ */
+ public function testIfExceptionIsThrownWhenMissingAnArgument()
+ {
+ $resolver = new ControllerResolver();
+ $request = Request::create('/');
+
+ $controller = array($this, 'controllerMethod1');
+
+ $resolver->getArguments($request, $controller);
+ }
+
/**
* @requires PHP 7.1
*/