diff --git a/src/Symfony/Component/Console/Helper/QuestionHelper.php b/src/Symfony/Component/Console/Helper/QuestionHelper.php
index faf2648bd1..40709cedd9 100644
--- a/src/Symfony/Component/Console/Helper/QuestionHelper.php
+++ b/src/Symfony/Component/Console/Helper/QuestionHelper.php
@@ -166,15 +166,9 @@ class QuestionHelper extends Helper
$message = $question->getQuestion();
if ($question instanceof ChoiceQuestion) {
- $maxWidth = max(array_map([$this, 'strlen'], array_keys($question->getChoices())));
-
- $messages = (array) $question->getQuestion();
- foreach ($question->getChoices() as $key => $value) {
- $width = $maxWidth - $this->strlen($key);
- $messages[] = ' ['.$key.str_repeat(' ', $width).'] '.$value;
- }
-
- $output->writeln($messages);
+ $output->writeln(array_merge([
+ $question->getQuestion(),
+ ], $this->formatChoiceQuestionChoices($question, 'info')));
$message = $question->getPrompt();
}
@@ -182,6 +176,26 @@ class QuestionHelper extends Helper
$output->write($message);
}
+ /**
+ * @param string $tag
+ *
+ * @return string[]
+ */
+ protected function formatChoiceQuestionChoices(ChoiceQuestion $question, $tag)
+ {
+ $messages = [];
+
+ $maxWidth = max(array_map('self::strlen', array_keys($choices = $question->getChoices())));
+
+ foreach ($choices as $key => $value) {
+ $padding = str_repeat(' ', $maxWidth - self::strlen($key));
+
+ $messages[] = sprintf(" [<$tag>%s$padding$tag>] %s", $key, $value);
+ }
+
+ return $messages;
+ }
+
/**
* Outputs an error message.
*/
diff --git a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php
index 260c03e20b..e4e87b2f99 100644
--- a/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php
+++ b/src/Symfony/Component/Console/Helper/SymfonyQuestionHelper.php
@@ -68,15 +68,15 @@ class SymfonyQuestionHelper extends QuestionHelper
$output->writeln($text);
- if ($question instanceof ChoiceQuestion) {
- $width = max(array_map('strlen', array_keys($question->getChoices())));
+ $prompt = ' > ';
- foreach ($question->getChoices() as $key => $value) {
- $output->writeln(sprintf(" [%-${width}s] %s", $key, $value));
- }
+ if ($question instanceof ChoiceQuestion) {
+ $output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));
+
+ $prompt = $question->getPrompt();
}
- $output->write(' > ');
+ $output->write($prompt);
}
/**
diff --git a/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php b/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php
index cbf3b957b3..b7a26f9508 100644
--- a/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php
+++ b/src/Symfony/Component/Console/Tests/Helper/SymfonyQuestionHelperTest.php
@@ -130,6 +130,49 @@ class SymfonyQuestionHelperTest extends AbstractQuestionHelperTest
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new Question('What\'s your name?'));
}
+ public function testChoiceQuestionPadding()
+ {
+ $choiceQuestion = new ChoiceQuestion('qqq', [
+ 'foo' => 'foo',
+ 'żółw' => 'bar',
+ 'łabądź' => 'baz',
+ ]);
+
+ (new SymfonyQuestionHelper())->ask(
+ $this->createStreamableInputInterfaceMock($this->getInputStream("foo\n")),
+ $output = $this->createOutputInterface(),
+ $choiceQuestion
+ );
+
+ $this->assertOutputContains(<<
+EOT
+ , $output);
+ }
+
+ public function testChoiceQuestionCustomPrompt()
+ {
+ $choiceQuestion = new ChoiceQuestion('qqq', ['foo']);
+ $choiceQuestion->setPrompt(' >ccc> ');
+
+ (new SymfonyQuestionHelper())->ask(
+ $this->createStreamableInputInterfaceMock($this->getInputStream("foo\n")),
+ $output = $this->createOutputInterface(),
+ $choiceQuestion
+ );
+
+ $this->assertOutputContains(<<ccc>
+EOT
+ , $output);
+ }
+
protected function getInputStream($input)
{
$stream = fopen('php://memory', 'r+', false);
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php
index 45fbf238d7..5a2adddcd3 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php
@@ -52,14 +52,14 @@ class PassConfig
new ValidateEnvPlaceholdersPass(),
new ResolveChildDefinitionsPass(),
new RegisterServiceSubscribersPass(),
- new DecoratorServicePass(),
new ResolveParameterPlaceHoldersPass(false),
- new ResolveFactoryClassPass(),
new ResolveNamedArgumentsPass(),
- new AutowireRequiredMethodsPass(),
new ResolveBindingsPass(),
new ServiceLocatorTagPass(),
new CheckDefinitionValidityPass(),
+ new DecoratorServicePass(),
+ new ResolveFactoryClassPass(),
+ new AutowireRequiredMethodsPass(),
new AutowirePass(false),
new ResolveTaggedIteratorArgumentPass(),
new ResolveServiceSubscribersPass(),
diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php
index aeb9641811..14bf000863 100644
--- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php
+++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php
@@ -11,11 +11,14 @@
namespace Symfony\Component\DependencyInjection\Compiler;
+use Psr\Container\ContainerInterface as PsrContainerInterface;
+use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;
+use Symfony\Contracts\Service\ServiceProviderInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
/**
@@ -105,7 +108,14 @@ class RegisterServiceSubscribersPass extends AbstractRecursivePass
throw new InvalidArgumentException(sprintf('Service %s not exist in the map returned by "%s::getSubscribedServices()" for service "%s".', $message, $class, $this->currentId));
}
- $value->addTag('container.service_subscriber.locator', ['id' => (string) ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId)]);
+ $locatorRef = ServiceLocatorTagPass::register($this->container, $subscriberMap, $this->currentId);
+
+ $value->addTag('container.service_subscriber.locator', ['id' => (string) $locatorRef]);
+
+ $value->setBindings([
+ PsrContainerInterface::class => new BoundArgument($locatorRef, false),
+ ServiceProviderInterface::class => new BoundArgument($locatorRef, false),
+ ] + $value->getBindings());
return parent::processValue($value);
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php
index 0457f1febb..6b088af2c9 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/IntegrationTest.php
@@ -25,6 +25,7 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\BarTagClass;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedClass;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooBarTaggedForDefaultPriorityClass;
use Symfony\Component\DependencyInjection\Tests\Fixtures\FooTagClass;
+use Symfony\Contracts\Service\ServiceProviderInterface;
/**
* This class tests the integration of the different compiler passes.
@@ -143,6 +144,29 @@ class IntegrationTest extends TestCase
$this->assertInstanceOf(DecoratedServiceSubscriber::class, $container->get(ServiceSubscriberStub::class));
}
+ public function testCanDecorateServiceLocator()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('foo', 'stdClass')->setPublic(true);
+
+ $container->register(ServiceLocator::class)
+ ->addTag('container.service_locator')
+ ->setArguments([[new Reference('foo')]])
+ ;
+
+ $container->register(DecoratedServiceLocator::class)
+ ->setDecoratedService(ServiceLocator::class)
+ ->setPublic(true)
+ ->setArguments([new Reference(DecoratedServiceLocator::class.'.inner')])
+ ;
+
+ $container->compile();
+
+ $this->assertInstanceOf(DecoratedServiceLocator::class, $container->get(DecoratedServiceLocator::class));
+ $this->assertSame($container->get('foo'), $container->get(DecoratedServiceLocator::class)->get('foo'));
+ }
+
/**
* @dataProvider getYamlCompileTests
*/
@@ -441,6 +465,34 @@ class DecoratedServiceSubscriber
{
}
+class DecoratedServiceLocator implements ServiceProviderInterface
+{
+ /**
+ * @var ServiceLocator
+ */
+ private $locator;
+
+ public function __construct(ServiceLocator $locator)
+ {
+ $this->locator = $locator;
+ }
+
+ public function get($id)
+ {
+ return $this->locator->get($id);
+ }
+
+ public function has($id): bool
+ {
+ return $this->locator->has($id);
+ }
+
+ public function getProvidedServices(): array
+ {
+ return $this->locator->getProvidedServices();
+ }
+}
+
class IntegrationTestStub extends IntegrationTestStubParent
{
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php
index 734c00c266..77b1969f60 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php
@@ -16,6 +16,7 @@ use Psr\Container\ContainerInterface as PsrContainerInterface;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\AutowirePass;
use Symfony\Component\DependencyInjection\Compiler\RegisterServiceSubscribersPass;
+use Symfony\Component\DependencyInjection\Compiler\ResolveBindingsPass;
use Symfony\Component\DependencyInjection\Compiler\ResolveServiceSubscribersPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -235,4 +236,32 @@ class RegisterServiceSubscribersPassTest extends TestCase
];
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
}
+
+ public function testBinding()
+ {
+ $container = new ContainerBuilder();
+
+ $container->register('foo', TestServiceSubscriber::class)
+ ->addMethodCall('setServiceProvider')
+ ->addTag('container.service_subscriber')
+ ;
+
+ (new RegisterServiceSubscribersPass())->process($container);
+ (new ResolveBindingsPass())->process($container);
+
+ $foo = $container->getDefinition('foo');
+ $locator = $container->getDefinition((string) $foo->getMethodCalls()[0][1][0]);
+
+ $this->assertFalse($locator->isPublic());
+ $this->assertSame(ServiceLocator::class, $locator->getClass());
+
+ $expected = [
+ TestServiceSubscriber::class => new ServiceClosureArgument(new TypedReference(TestServiceSubscriber::class, TestServiceSubscriber::class)),
+ CustomDefinition::class => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
+ 'bar' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, 'bar')),
+ 'baz' => new ServiceClosureArgument(new TypedReference(CustomDefinition::class, CustomDefinition::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE, 'baz')),
+ ];
+
+ $this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
+ }
}
diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php
index 968ff85f04..6876fc5cca 100644
--- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php
+++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/TestServiceSubscriber.php
@@ -2,6 +2,7 @@
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
+use Symfony\Contracts\Service\ServiceProviderInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
class TestServiceSubscriber implements ServiceSubscriberInterface
@@ -10,6 +11,10 @@ class TestServiceSubscriber implements ServiceSubscriberInterface
{
}
+ public function setServiceProvider(ServiceProviderInterface $container)
+ {
+ }
+
public static function getSubscribedServices(): array
{
return [