feature #26499 [FrameworkBundle] Allow fetching private services from test clients (nicolas-grekas)
This PR was merged into the 4.1-dev branch.
Discussion
----------
[FrameworkBundle] Allow fetching private services from test clients
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #25814
| License | MIT
| Doc PR | -
With this PR, `$client->getContainer()` returns a special container that gives access to private services as if they were public.
Tests derived from `WebTestCase` and `KernelTestCase` can access this special container by using the new `static::$container` property.
Commits
-------
a840809
[FrameworkBundle] Allow fetching private services from test clients
This commit is contained in:
commit
5cf0a2ea8f
@ -30,13 +30,15 @@ class Client extends BaseClient
|
|||||||
private $hasPerformedRequest = false;
|
private $hasPerformedRequest = false;
|
||||||
private $profiler = false;
|
private $profiler = false;
|
||||||
private $reboot = true;
|
private $reboot = true;
|
||||||
|
private $container;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function __construct(KernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null)
|
public function __construct(KernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null, ContainerInterface $container = null)
|
||||||
{
|
{
|
||||||
parent::__construct($kernel, $server, $history, $cookieJar);
|
parent::__construct($kernel, $server, $history, $cookieJar);
|
||||||
|
$this->container = $container;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,7 +48,7 @@ class Client extends BaseClient
|
|||||||
*/
|
*/
|
||||||
public function getContainer()
|
public function getContainer()
|
||||||
{
|
{
|
||||||
return $this->kernel->getContainer();
|
return $this->container ?? $this->kernel->getContainer();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class TestServiceContainerRealRefPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
if (!$container->hasDefinition('test.service_container')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$testContainer = $container->getDefinition('test.service_container');
|
||||||
|
$privateContainer = $container->getDefinition((string) $testContainer->getArgument(2));
|
||||||
|
$definitions = $container->getDefinitions();
|
||||||
|
|
||||||
|
foreach ($privateContainer->getArgument(0) as $id => $argument) {
|
||||||
|
if (isset($definitions[$target = (string) $argument->getValues()[0]])) {
|
||||||
|
$argument->setValues(array(new Reference($target)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler;
|
||||||
|
|
||||||
|
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
|
||||||
|
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
|
use Symfony\Component\DependencyInjection\Reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class TestServiceContainerWeakRefPass implements CompilerPassInterface
|
||||||
|
{
|
||||||
|
public function process(ContainerBuilder $container)
|
||||||
|
{
|
||||||
|
if (!$container->hasDefinition('test.service_container')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$privateServices = array();
|
||||||
|
$definitions = $container->getDefinitions();
|
||||||
|
|
||||||
|
foreach ($definitions as $id => $definition) {
|
||||||
|
if (!$definition->isPublic() && !$definition->getErrors() && !$definition->isAbstract()) {
|
||||||
|
$privateServices[$id] = new ServiceClosureArgument(new Reference($id, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$aliases = $container->getAliases();
|
||||||
|
|
||||||
|
foreach ($aliases as $id => $alias) {
|
||||||
|
if (!$alias->isPublic()) {
|
||||||
|
while (isset($aliases[$target = (string) $alias])) {
|
||||||
|
$alias = $aliases[$target];
|
||||||
|
}
|
||||||
|
if (isset($definitions[$target]) && !$definitions[$target]->getErrors() && !$definitions[$target]->isAbstract()) {
|
||||||
|
$privateServices[$id] = new ServiceClosureArgument(new Reference($target, ContainerBuilder::IGNORE_ON_UNINITIALIZED_REFERENCE));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($privateServices) {
|
||||||
|
$definitions[(string) $definitions['test.service_container']->getArgument(2)]->replaceArgument(0, $privateServices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,8 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass;
|
|||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\LoggingTranslatorPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddExpressionLanguageProvidersPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ContainerBuilderDebugDumpPass;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerWeakRefPass;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TestServiceContainerRealRefPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass;
|
||||||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass;
|
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\WorkflowGuardListenerPass;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
@ -114,6 +116,8 @@ class FrameworkBundle extends Bundle
|
|||||||
$this->addCompilerPassIfExists($container, FormPass::class);
|
$this->addCompilerPassIfExists($container, FormPass::class);
|
||||||
$container->addCompilerPass(new WorkflowGuardListenerPass());
|
$container->addCompilerPass(new WorkflowGuardListenerPass());
|
||||||
$container->addCompilerPass(new ResettableServicePass());
|
$container->addCompilerPass(new ResettableServicePass());
|
||||||
|
$container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32);
|
||||||
|
$container->addCompilerPass(new TestServiceContainerRealRefPass(), PassConfig::TYPE_AFTER_REMOVING);
|
||||||
|
|
||||||
if ($container->getParameter('kernel.debug')) {
|
if ($container->getParameter('kernel.debug')) {
|
||||||
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
$container->addCompilerPass(new AddDebugLogProcessorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
<argument>%test.client.parameters%</argument>
|
<argument>%test.client.parameters%</argument>
|
||||||
<argument type="service" id="test.client.history" />
|
<argument type="service" id="test.client.history" />
|
||||||
<argument type="service" id="test.client.cookiejar" />
|
<argument type="service" id="test.client.cookiejar" />
|
||||||
|
<argument type="service" id="test.service_container" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service id="test.client.history" class="Symfony\Component\BrowserKit\History" shared="false" />
|
<service id="test.client.history" class="Symfony\Component\BrowserKit\History" shared="false" />
|
||||||
@ -33,5 +34,15 @@
|
|||||||
</service>
|
</service>
|
||||||
</argument>
|
</argument>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
|
<service id="test.service_container" class="Symfony\Bundle\FrameworkBundle\Test\TestContainer" public="true">
|
||||||
|
<argument type="service" id="parameter_bag" on-invalid="null" />
|
||||||
|
<argument type="service" id="service_container" />
|
||||||
|
<argument type="service">
|
||||||
|
<service class="Symfony\Component\DependencyInjection\ServiceLocator">
|
||||||
|
<argument type="collection" />
|
||||||
|
</service>
|
||||||
|
</argument>
|
||||||
|
</service>
|
||||||
</services>
|
</services>
|
||||||
</container>
|
</container>
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
namespace Symfony\Bundle\FrameworkBundle\Test;
|
namespace Symfony\Bundle\FrameworkBundle\Test;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
use Symfony\Component\DependencyInjection\ResettableContainerInterface;
|
use Symfony\Component\DependencyInjection\ResettableContainerInterface;
|
||||||
use Symfony\Component\HttpKernel\KernelInterface;
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
|
||||||
@ -29,6 +30,11 @@ abstract class KernelTestCase extends TestCase
|
|||||||
*/
|
*/
|
||||||
protected static $kernel;
|
protected static $kernel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ContainerInterface
|
||||||
|
*/
|
||||||
|
protected static $container;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string The Kernel class name
|
* @return string The Kernel class name
|
||||||
*
|
*
|
||||||
@ -60,6 +66,9 @@ abstract class KernelTestCase extends TestCase
|
|||||||
static::$kernel = static::createKernel($options);
|
static::$kernel = static::createKernel($options);
|
||||||
static::$kernel->boot();
|
static::$kernel->boot();
|
||||||
|
|
||||||
|
$container = static::$kernel->getContainer();
|
||||||
|
static::$container = $container->has('test.service_container') ? $container->get('test.service_container') : $container;
|
||||||
|
|
||||||
return static::$kernel;
|
return static::$kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
105
src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php
Normal file
105
src/Symfony/Bundle/FrameworkBundle/Test/TestContainer.php
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien@symfony.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Bundle\FrameworkBundle\Test;
|
||||||
|
|
||||||
|
use Psr\Container\ContainerInterface as PsrContainerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\Container;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface as SymfonyContainerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Nicolas Grekas <p@tchwork.com>
|
||||||
|
*/
|
||||||
|
class TestContainer extends Container
|
||||||
|
{
|
||||||
|
private $publicContainer;
|
||||||
|
private $privateContainer;
|
||||||
|
|
||||||
|
public function __construct(?ParameterBagInterface $parameterBag, SymfonyContainerInterface $publicContainer, PsrContainerInterface $privateContainer)
|
||||||
|
{
|
||||||
|
$this->parameterBag = $parameterBag ?? $publicContainer->getParameterBag();
|
||||||
|
$this->publicContainer = $publicContainer;
|
||||||
|
$this->privateContainer = $privateContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function compile()
|
||||||
|
{
|
||||||
|
$this->publicContainer->compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isCompiled()
|
||||||
|
{
|
||||||
|
return $this->publicContainer->isCompiled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function set($id, $service)
|
||||||
|
{
|
||||||
|
$this->publicContainer->set($id, $service);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function has($id)
|
||||||
|
{
|
||||||
|
return $this->publicContainer->has($id) || $this->privateContainer->has($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function get($id, $invalidBehavior = /* self::EXCEPTION_ON_INVALID_REFERENCE */ 1)
|
||||||
|
{
|
||||||
|
return $this->privateContainer->has($id) ? $this->privateContainer->get($id) : $this->publicContainer->get($id, $invalidBehavior);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function initialized($id)
|
||||||
|
{
|
||||||
|
return $this->publicContainer->initialized($id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function reset()
|
||||||
|
{
|
||||||
|
$this->publicContainer->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getServiceIds()
|
||||||
|
{
|
||||||
|
return $this->publicContainer->getServiceIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getRemovedIds()
|
||||||
|
{
|
||||||
|
return $this->publicContainer->getRemovedIds();
|
||||||
|
}
|
||||||
|
}
|
@ -18,9 +18,8 @@ class PropertyInfoTest extends WebTestCase
|
|||||||
public function testPhpDocPriority()
|
public function testPhpDocPriority()
|
||||||
{
|
{
|
||||||
static::bootKernel(array('test_case' => 'Serializer'));
|
static::bootKernel(array('test_case' => 'Serializer'));
|
||||||
$container = static::$kernel->getContainer();
|
|
||||||
|
|
||||||
$this->assertEquals(array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT))), $container->get('test.property_info')->getTypes('Symfony\Bundle\FrameworkBundle\Tests\Functional\Dummy', 'codes'));
|
$this->assertEquals(array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_INT))), static::$container->get('property_info')->getTypes('Symfony\Bundle\FrameworkBundle\Tests\Functional\Dummy', 'codes'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
imports:
|
imports:
|
||||||
- { resource: ../config/default.yml }
|
- { resource: ../config/default.yml }
|
||||||
|
|
||||||
services:
|
|
||||||
_defaults: { public: true }
|
|
||||||
test.property_info: '@property_info'
|
|
||||||
|
|
||||||
framework:
|
framework:
|
||||||
serializer: { enabled: true }
|
serializer: { enabled: true }
|
||||||
property_info: { enabled: true }
|
property_info: { enabled: true }
|
||||||
|
@ -4,7 +4,7 @@ framework:
|
|||||||
validation: { enabled: true, enable_annotations: true }
|
validation: { enabled: true, enable_annotations: true }
|
||||||
csrf_protection: true
|
csrf_protection: true
|
||||||
form: true
|
form: true
|
||||||
test: ~
|
test: true
|
||||||
default_locale: en
|
default_locale: en
|
||||||
session:
|
session:
|
||||||
storage_id: session.storage.mock_file
|
storage_id: session.storage.mock_file
|
||||||
|
Reference in New Issue
Block a user