feature #19191 [DependencyInjection] Automatically detect the definitions class when possible (Ener-Getick)
This PR was merged into the 3.2-dev branch.
Discussion
----------
[DependencyInjection] Automatically detect the definitions class when possible
| Q | A
| ------------- | ---
| Branch? | "master"
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | https://github.com/symfony/symfony/issues/19161
| License | MIT
| Doc PR |
> Thanks to the features of php 7.0 we can now guess the class of a service created with a factory:
> ```php
> function myFactory(): MyServiceClass
> {
> }
> ```
>
> So I propose to create a new pass to automatically update the services definition when possible. This is particularly useful for autowiring (this way you don't have to copy-paste the class name of the service, especially when this is from a third party library).
>
> What do you think ?
Commits
-------
63afe3c
[DependencyInjection] Automatically detect the definitions class when possible
This commit is contained in:
commit
36c399f632
@ -0,0 +1,89 @@
|
||||
<?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\Component\DependencyInjection\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* @author Guilhem N. <egetick@gmail.com>
|
||||
*/
|
||||
class FactoryReturnTypePass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
// works only since php 7.0 and hhvm 3.11
|
||||
if (!method_exists(\ReflectionMethod::class, 'getReturnType')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($container->getDefinitions() as $id => $definition) {
|
||||
$this->updateDefinition($container, $id, $definition);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateDefinition(ContainerBuilder $container, $id, Definition $definition, array $previous = array())
|
||||
{
|
||||
// circular reference
|
||||
if (isset($previous[$id])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$factory = $definition->getFactory();
|
||||
if (null === $factory || null !== $definition->getClass()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$class = null;
|
||||
if (is_string($factory)) {
|
||||
try {
|
||||
$m = new \ReflectionFunction($factory);
|
||||
} catch (\ReflectionException $e) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if ($factory[0] instanceof Reference) {
|
||||
$previous[$id] = true;
|
||||
$factoryDefinition = $container->findDefinition((string) $factory[0]);
|
||||
$this->updateDefinition($container, (string) $factory[0], $factoryDefinition, $previous);
|
||||
$class = $factoryDefinition->getClass();
|
||||
} else {
|
||||
$class = $factory[0];
|
||||
}
|
||||
|
||||
try {
|
||||
$m = new \ReflectionMethod($class, $factory[1]);
|
||||
} catch (\ReflectionException $e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$returnType = $m->getReturnType();
|
||||
if (null !== $returnType && !$returnType->isBuiltin()) {
|
||||
$returnType = (string) $returnType;
|
||||
if (null !== $class) {
|
||||
$declaringClass = $m->getDeclaringClass()->getName();
|
||||
if ('self' === $returnType) {
|
||||
$returnType = $declaringClass;
|
||||
} elseif ('parent' === $returnType) {
|
||||
$returnType = get_parent_class($declaringClass) ?: null;
|
||||
}
|
||||
}
|
||||
|
||||
$definition->setClass($returnType);
|
||||
}
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ class PassConfig
|
||||
new CheckDefinitionValidityPass(),
|
||||
new ResolveReferencesToAliasesPass(),
|
||||
new ResolveInvalidReferencesPass(),
|
||||
new FactoryReturnTypePass(),
|
||||
new AutowirePass(),
|
||||
new AnalyzeServiceReferencesPass(true),
|
||||
new CheckCircularReferencesPass(),
|
||||
|
@ -0,0 +1,103 @@
|
||||
<?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\Component\DependencyInjection\Tests\Compiler;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\FactoryReturnTypePass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\factoryFunction;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\FactoryDummy;
|
||||
use Symfony\Component\DependencyInjection\Tests\Fixtures\FactoryParent;
|
||||
|
||||
/**
|
||||
* @author Guilhem N. <egetick@gmail.com>
|
||||
*/
|
||||
class FactoryReturnTypePassTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testProcess()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$factory = $container->register('factory');
|
||||
$factory->setFactory(array(FactoryDummy::class, 'createFactory'));
|
||||
|
||||
$foo = $container->register('foo');
|
||||
$foo->setFactory(array(new Reference('factory'), 'create'));
|
||||
|
||||
$bar = $container->register('bar', __CLASS__);
|
||||
$bar->setFactory(array(new Reference('factory'), 'create'));
|
||||
|
||||
$pass = new FactoryReturnTypePass();
|
||||
$pass->process($container);
|
||||
|
||||
if (method_exists(\ReflectionMethod::class, 'getReturnType')) {
|
||||
$this->assertEquals(FactoryDummy::class, $factory->getClass());
|
||||
$this->assertEquals(\stdClass::class, $foo->getClass());
|
||||
} else {
|
||||
$this->assertNull($factory->getClass());
|
||||
$this->assertNull($foo->getClass());
|
||||
}
|
||||
$this->assertEquals(__CLASS__, $bar->getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider returnTypesProvider
|
||||
*/
|
||||
public function testReturnTypes($factory, $returnType, $hhvmSupport = true)
|
||||
{
|
||||
if (!$hhvmSupport && defined('HHVM_VERSION')) {
|
||||
$this->markTestSkipped('Scalar typehints not supported by hhvm.');
|
||||
}
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$service = $container->register('service');
|
||||
$service->setFactory($factory);
|
||||
|
||||
$pass = new FactoryReturnTypePass();
|
||||
$pass->process($container);
|
||||
|
||||
if (method_exists(\ReflectionMethod::class, 'getReturnType')) {
|
||||
$this->assertEquals($returnType, $service->getClass());
|
||||
} else {
|
||||
$this->assertNull($service->getClass());
|
||||
}
|
||||
}
|
||||
|
||||
public function returnTypesProvider()
|
||||
{
|
||||
return array(
|
||||
// must be loaded before the function as they are in the same file
|
||||
array(array(FactoryDummy::class, 'createBuiltin'), null, false),
|
||||
array(array(FactoryDummy::class, 'createParent'), FactoryParent::class),
|
||||
array(array(FactoryDummy::class, 'createSelf'), FactoryDummy::class),
|
||||
array(factoryFunction::class, FactoryDummy::class),
|
||||
);
|
||||
}
|
||||
|
||||
public function testCircularReference()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
||||
$factory = $container->register('factory');
|
||||
$factory->setFactory(array(new Reference('factory2'), 'createSelf'));
|
||||
|
||||
$factory2 = $container->register('factory2');
|
||||
$factory2->setFactory(array(new Reference('factory'), 'create'));
|
||||
|
||||
$pass = new FactoryReturnTypePass();
|
||||
$pass->process($container);
|
||||
|
||||
$this->assertNull($factory->getClass());
|
||||
$this->assertNull($factory2->getClass());
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
<?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\Component\DependencyInjection\Tests\Fixtures;
|
||||
|
||||
class FactoryDummy extends FactoryParent
|
||||
{
|
||||
public static function createFactory(): FactoryDummy
|
||||
{
|
||||
}
|
||||
|
||||
public function create(): \stdClass
|
||||
{
|
||||
}
|
||||
|
||||
// Not supported by hhvm
|
||||
public function createBuiltin(): int
|
||||
{
|
||||
}
|
||||
|
||||
public static function createSelf(): self
|
||||
{
|
||||
}
|
||||
|
||||
public static function createParent(): parent
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class FactoryParent
|
||||
{
|
||||
}
|
||||
|
||||
function factoryFunction(): FactoryDummy
|
||||
{
|
||||
}
|
Reference in New Issue
Block a user