bug #39873 [DependencyInjection] Fix container injection with TypedReference (jderusse)

This PR was merged into the 5.1 branch.

Discussion
----------

[DependencyInjection] Fix container injection with TypedReference

| Q             | A
| ------------- | ---
| Branch?       | 5.1
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Tickets       | fix #
| License       | MIT
| Doc PR        | -

When using `TypedReference`, the closure signature is `function (...): Type {` which does not matche the regular expresion that replace `$this` by `$container` + `use ($container)`

note: there is no issue in 4.4. At that time, dumped container use `$this->services` and looks like:
```
$instance->closures = [0 => function (): ?\stdClass {\n
    return ($this->services['foo'] ?? null);\n
}];
```

Commits
-------

f8c14acd51 Fix container injection with TypedReference
This commit is contained in:
Nicolas Grekas 2021-01-18 19:01:07 +01:00
commit b4ec36fad4
3 changed files with 189 additions and 3 deletions

View File

@ -904,7 +904,7 @@ EOF;
$factoryCode = $asFile ? 'self::do($container, false)' : sprintf('$this->%s(false)', $methodName);
$factoryCode = $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode);
$code .= $asFile ? preg_replace('/function \(([^)]*+)\) {/', 'function (\1) use ($container) {', $factoryCode) : $factoryCode;
$code .= $asFile ? preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $factoryCode) : $factoryCode;
}
$c = $this->addServiceInclude($id, $definition);
@ -934,8 +934,7 @@ EOF;
if ($asFile) {
$code = str_replace('$this', '$container', $code);
$code = str_replace('function () {', 'function () use ($container) {', $code);
$code = str_replace('function ($lazyLoad = true) {', 'function ($lazyLoad = true) use ($container) {', $code);
$code = preg_replace('/function \(([^)]*+)\)( {|:)/', 'function (\1) use ($container)\2', $code);
}
$code .= " }\n";

View File

@ -244,6 +244,26 @@ class PhpDumperTest extends TestCase
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services9_as_files.txt', $dump);
}
public function testDumpAsFilesWithTypedReference()
{
$container = include self::$fixturesPath.'/containers/container10.php';
$container->getDefinition('foo')->addTag('hot');
$container->register('bar', 'stdClass');
$container->register('closure', 'stdClass')
->setProperty('closures', [
new ServiceClosureArgument(new TypedReference('foo', \stdClass::class, $container::IGNORE_ON_UNINITIALIZED_REFERENCE)),
])
->setPublic(true);
$container->compile();
$dumper = new PhpDumper($container);
$dump = print_r($dumper->dump(['as_files' => true, 'file' => __DIR__, 'hot_path_tag' => 'hot', 'inline_factories_parameter' => false, 'inline_class_loader_parameter' => false]), true);
if ('\\' === \DIRECTORY_SEPARATOR) {
$dump = str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump);
}
$this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services10_as_files.txt', $dump);
}
public function testDumpAsFilesWithFactoriesInlined()
{
$container = include self::$fixturesPath.'/containers/container9.php';

View File

@ -0,0 +1,167 @@
Array
(
[Container%s/removed-ids.php] => <?php
namespace Container%s;
return [
'Psr\\Container\\ContainerInterface' => true,
'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
'bar' => true,
];
[Container%s/getClosureService.php] => <?php
namespace Container%s;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
/**
* @internal This class has been auto-generated by the Symfony Dependency Injection Component.
*/
class getClosureService extends ProjectServiceContainer
{
/**
* Gets the public 'closure' shared service.
*
* @return \stdClass
*/
public static function do($container, $lazyLoad = true)
{
$container->services['closure'] = $instance = new \stdClass();
$instance->closures = [0 => function () use ($container): ?\stdClass {
return ($container->services['foo'] ?? null);
}];
return $instance;
}
}
[Container%s/ProjectServiceContainer.php] => <?php
namespace Container%s;
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
/**
* @internal This class has been auto-generated by the Symfony Dependency Injection Component.
*/
class ProjectServiceContainer extends Container
{
protected $containerDir;
protected $targetDir;
protected $parameters = [];
private $buildParameters;
public function __construct(array $buildParameters = [], $containerDir = __DIR__)
{
$this->buildParameters = $buildParameters;
$this->containerDir = $containerDir;
$this->targetDir = \dirname($containerDir);
$this->services = $this->privates = [];
$this->methodMap = [
'foo' => 'getFooService',
];
$this->fileMap = [
'closure' => 'getClosureService',
];
$this->aliases = [];
}
public function compile(): void
{
throw new LogicException('You cannot compile a dumped container that was already compiled.');
}
public function isCompiled(): bool
{
return true;
}
public function getRemovedIds(): array
{
return require $this->containerDir.\DIRECTORY_SEPARATOR.'removed-ids.php';
}
protected function load($file, $lazyLoad = true)
{
if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) {
return $class::do($this, $lazyLoad);
}
if ('.' === $file[-4]) {
$class = substr($class, 0, -4);
} else {
$file .= '.php';
}
$service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file;
return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service;
}
/**
* Gets the public 'foo' shared service.
*
* @return \FooClass
*/
protected function getFooService()
{
return $this->services['foo'] = new \FooClass(new \stdClass());
}
}
[ProjectServiceContainer.preload.php] => <?php
// This file has been auto-generated by the Symfony Dependency Injection Component
// You can reference it in the "opcache.preload" php.ini setting on PHP >= 7.4 when preloading is desired
use Symfony\Component\DependencyInjection\Dumper\Preloader;
if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
return;
}
require dirname(__DIR__, %d).'%svendor/autoload.php';
require __DIR__.'/Container%s/ProjectServiceContainer.php';
require __DIR__.'/Container%s/getClosureService.php';
$classes = [];
$classes[] = 'FooClass';
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
Preloader::preload($classes);
[ProjectServiceContainer.php] => <?php
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
if (\class_exists(\Container%s\ProjectServiceContainer::class, false)) {
// no-op
} elseif (!include __DIR__.'/Container%s/ProjectServiceContainer.php') {
touch(__DIR__.'/Container%s.legacy');
return;
}
if (!\class_exists(ProjectServiceContainer::class, false)) {
\class_alias(\Container%s\ProjectServiceContainer::class, ProjectServiceContainer::class, false);
}
return new \Container%s\ProjectServiceContainer([
'container.build_hash' => '%s',
'container.build_id' => '%s',
'container.build_time' => %d,
], __DIR__.\DIRECTORY_SEPARATOR.'Container%s');
)