Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fd0d04c

Browse files
[FrameworkBundle] Allow setting private services with the test container
1 parent 21191d5 commit fd0d04c

File tree

6 files changed

+67
-18
lines changed

6 files changed

+67
-18
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
* Add support to pass namespace wildcard in `framework.messenger.routing`
1212
* Deprecate `framework:exceptions` tag, unwrap it and replace `framework:exception` tags' `name` attribute by `class`
1313
* Deprecate the `notifier.logger_notification_listener` service, use the `notifier.notification_logger_listener` service instead
14+
* Allow setting private services with the test container
1415

1516
6.2
1617
---

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerRealRefPass.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
1415
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1516
use Symfony\Component\DependencyInjection\ContainerBuilder;
1617
use Symfony\Component\DependencyInjection\Reference;
@@ -29,15 +30,30 @@ public function process(ContainerBuilder $container)
2930
$privateContainer = $container->getDefinition('test.private_services_locator');
3031
$definitions = $container->getDefinitions();
3132
$privateServices = $privateContainer->getArgument(0);
33+
$renamedIds = [];
3234

3335
foreach ($privateServices as $id => $argument) {
3436
if (isset($definitions[$target = (string) $argument->getValues()[0]])) {
3537
$argument->setValues([new Reference($target)]);
38+
if ($id !== $target) {
39+
$renamedIds[$id] = $target;
40+
}
3641
} else {
3742
unset($privateServices[$id]);
3843
}
3944
}
4045

46+
foreach ($container->getAliases() as $id => $target) {
47+
if ($definitions[(string) $target]->hasTag('container.private')) {
48+
$privateServices[$id] = new ServiceClosureArgument(new Reference($target));
49+
}
50+
$renamedIds[$id] = (string) $target;
51+
}
52+
4153
$privateContainer->replaceArgument(0, $privateServices);
54+
55+
if ($container->hasDefinition('test.service_container') && $renamedIds) {
56+
$container->getDefinition('test.service_container')->setArgument(2, $renamedIds);
57+
}
4258
}
4359
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TestServiceContainerWeakRefPass.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1515
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
17-
use Symfony\Component\DependencyInjection\Definition;
1817
use Symfony\Component\DependencyInjection\Reference;
1918

2019
/**
@@ -30,10 +29,9 @@ public function process(ContainerBuilder $container)
3029

3130
$privateServices = [];
3231
$definitions = $container->getDefinitions();
33-
$hasErrors = method_exists(Definition::class, 'hasErrors') ? 'hasErrors' : 'getErrors';
3432

3533
foreach ($definitions as $id => $definition) {
36-
if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) && !$definition->$hasErrors() && !$definition->isAbstract()) {
34+
if ($id && '.' !== $id[0] && (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) && !$definition->hasErrors() && !$definition->isAbstract()) {
3735
$privateServices[$id] = new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE);
3836
}
3937
}
@@ -45,7 +43,7 @@ public function process(ContainerBuilder $container)
4543
while (isset($aliases[$target = (string) $alias])) {
4644
$alias = $aliases[$target];
4745
}
48-
if (isset($definitions[$target]) && !$definitions[$target]->$hasErrors() && !$definitions[$target]->isAbstract()) {
46+
if (isset($definitions[$target]) && !$definitions[$target]->hasErrors() && !$definitions[$target]->isAbstract()) {
4947
$privateServices[$id] = new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE);
5048
}
5149
}

src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313

1414
use Psr\Container\ContainerInterface;
1515
use Symfony\Component\DependencyInjection\Container;
16+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
1617
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
1718
use Symfony\Component\HttpKernel\KernelInterface;
1819

1920
/**
2021
* A special container used in tests. This gives access to both public and
21-
* private services. The container will not include private services that has
22+
* private services. The container will not include private services that have
2223
* been inlined or removed. Private services will be removed when they are not
2324
* used by other services.
2425
*
@@ -28,13 +29,11 @@
2829
*/
2930
class TestContainer extends Container
3031
{
31-
private KernelInterface $kernel;
32-
private string $privateServicesLocatorId;
33-
34-
public function __construct(KernelInterface $kernel, string $privateServicesLocatorId)
35-
{
36-
$this->kernel = $kernel;
37-
$this->privateServicesLocatorId = $privateServicesLocatorId;
32+
public function __construct(
33+
private KernelInterface $kernel,
34+
private string $privateServicesLocatorId,
35+
private array $renamedIds = [],
36+
) {
3837
}
3938

4039
public function compile()
@@ -69,7 +68,20 @@ public function setParameter(string $name, mixed $value)
6968

7069
public function set(string $id, mixed $service)
7170
{
72-
$this->getPublicContainer()->set($id, $service);
71+
$container = $this->getPublicContainer();
72+
$renamedId = $this->renamedIds[$id] ?? $id;
73+
74+
try {
75+
$container->set($renamedId, $service);
76+
} catch (InvalidArgumentException $e) {
77+
if (!str_starts_with($e->getMessage(), "The \"$renamedId\" service is private")) {
78+
throw $e;
79+
}
80+
if (isset($container->privates[$renamedId])) {
81+
throw new InvalidArgumentException(sprintf('The "%s" service is already initialized, you cannot replace it.', $id));
82+
}
83+
$container->privates[$renamedId] = $service;
84+
}
7385
}
7486

7587
public function has(string $id): bool
@@ -104,11 +116,7 @@ public function getRemovedIds(): array
104116

105117
private function getPublicContainer(): Container
106118
{
107-
if (null === $container = $this->kernel->getContainer()) {
108-
throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?');
109-
}
110-
111-
return $container;
119+
return $this->kernel->getContainer() ?? throw new \LogicException('Cannot access the container on a non-booted kernel. Did you forget to boot it?');
112120
}
113121

114122
private function getPrivateContainer(): ContainerInterface

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TestServiceContainerRefPassesTest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ public function testProcess()
4444
->setPublic(true)
4545
->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.42'])
4646
;
47+
$container->register('Test\soon_private_service_decorated')
48+
->setPublic(true)
49+
->addTag('container.private', ['package' => 'foo/bar', 'version' => '1.42'])
50+
;
51+
$container->register('Test\soon_private_service_decorator')
52+
->setDecoratedService('Test\soon_private_service_decorated')
53+
->setArguments(['Test\soon_private_service_decorator.inner']);
4754

4855
$container->register('Test\private_used_shared_service');
4956
$container->register('Test\private_unused_shared_service');
@@ -56,6 +63,8 @@ public function testProcess()
5663
'Test\private_used_shared_service' => new ServiceClosureArgument(new Reference('Test\private_used_shared_service')),
5764
'Test\private_used_non_shared_service' => new ServiceClosureArgument(new Reference('Test\private_used_non_shared_service')),
5865
'Test\soon_private_service' => new ServiceClosureArgument(new Reference('.container.private.Test\soon_private_service')),
66+
'Test\soon_private_service_decorator' => new ServiceClosureArgument(new Reference('.container.private.Test\soon_private_service_decorated')),
67+
'Test\soon_private_service_decorated' => new ServiceClosureArgument(new Reference('.container.private.Test\soon_private_service_decorated')),
5968
];
6069

6170
$privateServices = $container->getDefinition('test.private_services_locator')->getArgument(0);

src/Symfony/Bundle/FrameworkBundle/Tests/Functional/KernelTestCaseTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\PublicService;
1818
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestServiceContainer\UnusedPrivateService;
1919
use Symfony\Component\DependencyInjection\ContainerInterface;
20+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
2021

2122
class KernelTestCaseTest extends AbstractWebTestCase
2223
{
@@ -41,4 +42,20 @@ public function testThatPrivateServicesAreAvailableIfTestConfigIsEnabled()
4142
$this->assertTrue($container->has('private_service'));
4243
$this->assertFalse($container->has(UnusedPrivateService::class));
4344
}
45+
46+
public function testThatPrivateServicesCanBeSetIfTestConfigIsEnabled()
47+
{
48+
static::bootKernel(['test_case' => 'TestServiceContainer']);
49+
50+
$container = static::getContainer();
51+
52+
$service = new \stdClass();
53+
54+
$container->set('private_service', $service);
55+
$this->assertSame($service, $container->get('private_service'));
56+
57+
$this->expectException(InvalidArgumentException::class);
58+
$this->expectExceptionMessage('The "private_service" service is already initialized, you cannot replace it.');
59+
$container->set('private_service', new \stdClass());
60+
}
4461
}

0 commit comments

Comments
 (0)