Merge branch '4.3' into 4.4
* 4.3: [DI] Service locators can't be decorated [Console][SymfonyQuestionHelper] Handle multibytes question choices keys and custom prompt [DI] fix auto-binding service providers to their service subscribers
This commit is contained in:
commit
4b93c6210d
@ -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[] = ' [<info>'.$key.str_repeat(' ', $width).'</info>] '.$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.
|
||||
*/
|
||||
|
@ -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(" [<comment>%-${width}s</comment>] %s", $key, $value));
|
||||
}
|
||||
if ($question instanceof ChoiceQuestion) {
|
||||
$output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));
|
||||
|
||||
$prompt = $question->getPrompt();
|
||||
}
|
||||
|
||||
$output->write(' > ');
|
||||
$output->write($prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
qqq:
|
||||
[foo ] foo
|
||||
[żółw ] bar
|
||||
[łabądź] baz
|
||||
>
|
||||
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(<<<EOT
|
||||
qqq:
|
||||
[0] foo
|
||||
>ccc>
|
||||
EOT
|
||||
, $output);
|
||||
}
|
||||
|
||||
protected function getInputStream($input)
|
||||
{
|
||||
$stream = fopen('php://memory', 'r+', false);
|
||||
|
@ -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(),
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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 [
|
||||
|
Reference in New Issue
Block a user