[DI] scope singly-implemented interfaces detection by file

This commit is contained in:
Daniel Iwaniec 2019-08-27 00:47:30 +02:00 committed by Nicolas Grekas
parent c1bca297cf
commit bec38900d8
24 changed files with 278 additions and 12 deletions

View File

@ -73,6 +73,8 @@ class ContainerConfigurator extends AbstractConfigurator
final public function services(): ServicesConfigurator
{
$this->loader->resetBeforeConfiguringServices();
return new ServicesConfigurator($this->container, $this->loader, $this->instanceof, $this->path, $this->anonymousCount);
}
}

View File

@ -105,6 +105,7 @@ class ServicesConfigurator extends AbstractConfigurator
$ref = static::processValue($referencedId, true);
$alias = new Alias((string) $ref, $this->defaults->isPublic());
$this->container->setAlias($id, $alias);
$this->loader->removeSinglyImplementedAlias((string) $ref);
return new AliasConfigurator($this, $alias);
}

View File

@ -29,6 +29,9 @@ abstract class FileLoader extends BaseFileLoader
protected $container;
protected $isLoadingInstanceof = false;
protected $instanceof = [];
protected $interfaces = [];
protected $singlyImplemented = [];
protected $singlyImplementedAliases = [];
public function __construct(ContainerBuilder $container, FileLocatorInterface $locator)
{
@ -57,12 +60,10 @@ abstract class FileLoader extends BaseFileLoader
$classes = $this->findClasses($namespace, $resource, (array) $exclude);
// prepare for deep cloning
$serializedPrototype = serialize($prototype);
$interfaces = [];
$singlyImplemented = [];
foreach ($classes as $class => $errorMessage) {
if (interface_exists($class, false)) {
$interfaces[] = $class;
$this->interfaces[] = $class;
} else {
$this->setDefinition($class, $definition = unserialize($serializedPrototype));
if (null !== $errorMessage) {
@ -71,14 +72,17 @@ abstract class FileLoader extends BaseFileLoader
continue;
}
foreach (class_implements($class, false) as $interface) {
$singlyImplemented[$interface] = isset($singlyImplemented[$interface]) ? false : $class;
$this->singlyImplemented[$interface] = isset($this->singlyImplemented[$interface]) ? false : $class;
}
}
}
foreach ($interfaces as $interface) {
if (!empty($singlyImplemented[$interface])) {
$this->container->setAlias($interface, $singlyImplemented[$interface])
->setPublic(false);
foreach ($this->interfaces as $interface) {
if (!empty($this->singlyImplemented[$interface]) && !$this->container->hasAlias($interface)) {
$this->container->setAlias($interface, $this->singlyImplemented[$interface])->setPublic(false);
$this->singlyImplementedAliases[$interface] = true;
} elseif ($this->singlyImplementedAliases[$interface] ?? false) {
$this->container->removeAlias($interface);
}
}
}

View File

@ -63,6 +63,18 @@ class PhpFileLoader extends FileLoader
return 'php' === $type;
}
public function resetBeforeConfiguringServices(): void
{
$this->interfaces = [];
$this->singlyImplemented = [];
$this->singlyImplementedAliases = [];
}
public function removeSinglyImplementedAlias(string $alias): void
{
unset($this->singlyImplementedAliases[$alias]);
}
}
/**

View File

@ -66,6 +66,9 @@ class XmlFileLoader extends FileLoader
$this->parseDefinitions($xml, $path, $defaults);
} finally {
$this->instanceof = [];
$this->interfaces = [];
$this->singlyImplemented = [];
$this->singlyImplementedAliases = [];
}
}
@ -194,6 +197,7 @@ class XmlFileLoader extends FileLoader
$this->validateAlias($service, $file);
$this->container->setAlias((string) $service->getAttribute('id'), $alias = new Alias($alias));
unset($this->singlyImplementedAliases[(string) $service->getAttribute('id')]);
if ($publicAttr = $service->getAttribute('public')) {
$alias->setPublic(XmlUtils::phpize($publicAttr));
} elseif (isset($defaults['public'])) {

View File

@ -150,6 +150,9 @@ class YamlFileLoader extends FileLoader
$this->parseDefinitions($content, $path);
} finally {
$this->instanceof = [];
$this->interfaces = [];
$this->singlyImplemented = [];
$this->singlyImplementedAliases = [];
}
}
@ -318,6 +321,7 @@ class YamlFileLoader extends FileLoader
if (\is_string($service) && 0 === strpos($service, '@')) {
$this->container->setAlias($id, $alias = new Alias(substr($service, 1)));
unset($this->singlyImplementedAliases[$id]);
if (isset($defaults['public'])) {
$alias->setPublic($defaults['public']);
}
@ -341,6 +345,7 @@ class YamlFileLoader extends FileLoader
if (isset($service['alias'])) {
$this->container->setAlias($id, $alias = new Alias($service['alias']));
unset($this->singlyImplementedAliases[$id]);
if (\array_key_exists('public', $service)) {
$alias->setPublic($service['public']);
} elseif (isset($defaults['public'])) {

View File

@ -0,0 +1,9 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter;
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\PortInterface;
class Adapter implements PortInterface
{
}

View File

@ -0,0 +1,9 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\AnotherAdapter;
use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\PortInterface;
class Adapter implements PortInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port;
interface PortInterface
{
}

View File

@ -9,7 +9,7 @@ return function (ContainerConfigurator $c) {
->tag('baz');
$di->load(Prototype::class.'\\', '../Prototype')
->autoconfigure()
->exclude('../Prototype/{OtherDir,BadClasses}')
->exclude('../Prototype/{OtherDir,BadClasses,SinglyImplementedInterface}')
->factory('f')
->deprecate('%service_id%')
->args([0])

View File

@ -9,7 +9,7 @@ return function (ContainerConfigurator $c) {
->tag('baz');
$di->load(Prototype::class.'\\', '../Prototype')
->autoconfigure()
->exclude(['../Prototype/OtherDir', '../Prototype/BadClasses'])
->exclude(['../Prototype/OtherDir', '../Prototype/BadClasses', '../Prototype/SinglyImplementedInterface'])
->factory('f')
->deprecate('%service_id%')
->args([0])

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autowire="true" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\"
resource="../Prototype/SinglyImplementedInterface/Adapter/*" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\AnotherAdapter\"
resource="../Prototype/SinglyImplementedInterface/AnotherAdapter/*" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\"
resource="../Prototype/SinglyImplementedInterface/Port/*" />
</services>
</container>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autowire="true" />
<service id="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\PortInterface"
alias="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\Adapter" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\"
resource="../Prototype/SinglyImplementedInterface/Port/*" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\"
resource="../Prototype/SinglyImplementedInterface/Adapter/*" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\AnotherAdapter\"
resource="../Prototype/SinglyImplementedInterface/AnotherAdapter/*" />
</services>
</container>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autowire="true" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\"
resource="../Prototype/SinglyImplementedInterface/Port/*" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\"
resource="../Prototype/SinglyImplementedInterface/Adapter/*" />
<service id="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\PortInterface"
alias="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\Adapter" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\AnotherAdapter\"
resource="../Prototype/SinglyImplementedInterface/AnotherAdapter/*" />
</services>
</container>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\" resource="../Prototype/*" exclude="../Prototype/{OtherDir,BadClasses}" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\" resource="../Prototype/*" exclude="../Prototype/{OtherDir,BadClasses,SinglyImplementedInterface}" />
</services>
</container>

View File

@ -4,6 +4,7 @@
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\" resource="../Prototype/*">
<exclude>../Prototype/OtherDir</exclude>
<exclude>../Prototype/BadClasses</exclude>
<exclude>../Prototype/SinglyImplementedInterface</exclude>
</prototype>
</services>
</container>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<defaults autowire="true" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\"
resource="../Prototype/SinglyImplementedInterface/Port/*" />
<prototype namespace="Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\"
resource="../Prototype/SinglyImplementedInterface/Adapter/*" />
</services>
</container>

View File

@ -0,0 +1,12 @@
services:
_defaults:
autowire: true
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\:
resource: ../Prototype/SinglyImplementedInterface/Adapter/*
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\AnotherAdapter\:
resource: ../Prototype/SinglyImplementedInterface/AnotherAdapter/*
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\:
resource: ../Prototype/SinglyImplementedInterface/Port/*

View File

@ -0,0 +1,15 @@
services:
_defaults:
autowire: true
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\PortInterface:
alias: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\Adapter
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\:
resource: ../Prototype/SinglyImplementedInterface/Port/*
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\:
resource: ../Prototype/SinglyImplementedInterface/Adapter/*
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\AnotherAdapter\:
resource: ../Prototype/SinglyImplementedInterface/AnotherAdapter/*

View File

@ -0,0 +1,15 @@
services:
_defaults:
autowire: true
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\:
resource: ../Prototype/SinglyImplementedInterface/Port/*
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\:
resource: ../Prototype/SinglyImplementedInterface/Adapter/*
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\PortInterface:
alias: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\Adapter
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\AnotherAdapter\:
resource: ../Prototype/SinglyImplementedInterface/AnotherAdapter/*

View File

@ -1,4 +1,4 @@
services:
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\:
resource: ../Prototype
exclude: '../Prototype/{OtherDir,BadClasses}'
exclude: '../Prototype/{OtherDir,BadClasses,SinglyImplementedInterface}'

View File

@ -0,0 +1,9 @@
services:
_defaults:
autowire: true
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Port\:
resource: ../Prototype/SinglyImplementedInterface/Port/*
Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\SinglyImplementedInterface\Adapter\:
resource: ../Prototype/SinglyImplementedInterface/Adapter/*

View File

@ -652,6 +652,7 @@ class XmlFileLoaderTest extends TestCase
[
str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'OtherDir') => true,
str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'BadClasses') => true,
str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'SinglyImplementedInterface') => true,
]
);
$this->assertContains((string) $globResource, $resources);
@ -684,6 +685,7 @@ class XmlFileLoaderTest extends TestCase
[
str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'BadClasses') => true,
str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'OtherDir') => true,
str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'SinglyImplementedInterface') => true,
]
);
$this->assertContains((string) $globResource, $resources);
@ -901,4 +903,50 @@ class XmlFileLoaderTest extends TestCase
$this->assertSame('overridden', $container->get('bar')->quz);
}
public function testSinglyImplementedInterfacesInMultipleResources()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('singly_implemented_interface_in_multiple_resources.xml');
$alias = $container->getAlias(Prototype\SinglyImplementedInterface\Port\PortInterface::class);
$this->assertSame(Prototype\SinglyImplementedInterface\Adapter\Adapter::class, (string) $alias);
}
public function testNotSinglyImplementedInterfacesInMultipleResources()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('not_singly_implemented_interface_in_multiple_resources.xml');
$this->assertFalse($container->hasAlias(Prototype\SinglyImplementedInterface\Port\PortInterface::class));
}
public function testNotSinglyImplementedInterfacesInMultipleResourcesWithPreviouslyRegisteredAlias()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('not_singly_implemented_interface_in_multiple_resources_with_previously_registered_alias.xml');
$alias = $container->getAlias(Prototype\SinglyImplementedInterface\Port\PortInterface::class);
$this->assertSame(Prototype\SinglyImplementedInterface\Adapter\Adapter::class, (string) $alias);
}
public function testNotSinglyImplementedInterfacesInMultipleResourcesWithPreviouslyRegisteredAlias2()
{
$container = new ContainerBuilder();
$loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml'));
$loader->load('not_singly_implemented_interface_in_multiple_resources_with_previously_registered_alias2.xml');
$alias = $container->getAlias(Prototype\SinglyImplementedInterface\Port\PortInterface::class);
$this->assertSame(Prototype\SinglyImplementedInterface\Adapter\Adapter::class, (string) $alias);
}
}

View File

@ -415,6 +415,7 @@ class YamlFileLoaderTest extends TestCase
false, [
str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'BadClasses') => true,
str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'OtherDir') => true,
str_replace(\DIRECTORY_SEPARATOR, '/', $prototypeRealPath.\DIRECTORY_SEPARATOR.'SinglyImplementedInterface') => true,
]
);
$this->assertContains((string) $globResource, $resources);
@ -834,4 +835,50 @@ class YamlFileLoaderTest extends TestCase
$this->assertInstanceOf(TaggedIteratorArgument::class, $iteratorArgument);
$this->assertNull($iteratorArgument->getIndexAttribute());
}
public function testSinglyImplementedInterfacesInMultipleResources()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('singly_implemented_interface_in_multiple_resources.yml');
$alias = $container->getAlias(Prototype\SinglyImplementedInterface\Port\PortInterface::class);
$this->assertSame(Prototype\SinglyImplementedInterface\Adapter\Adapter::class, (string) $alias);
}
public function testNotSinglyImplementedInterfacesInMultipleResources()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('not_singly_implemented_interface_in_multiple_resources.yml');
$this->assertFalse($container->hasAlias(Prototype\SinglyImplementedInterface\Port\PortInterface::class));
}
public function testNotSinglyImplementedInterfacesInMultipleResourcesWithPreviouslyRegisteredAlias()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('not_singly_implemented_interface_in_multiple_resources_with_previously_registered_alias.yml');
$alias = $container->getAlias(Prototype\SinglyImplementedInterface\Port\PortInterface::class);
$this->assertSame(Prototype\SinglyImplementedInterface\Adapter\Adapter::class, (string) $alias);
}
public function testNotSinglyImplementedInterfacesInMultipleResourcesWithPreviouslyRegisteredAlias2()
{
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
$loader->load('not_singly_implemented_interface_in_multiple_resources_with_previously_registered_alias2.yml');
$alias = $container->getAlias(Prototype\SinglyImplementedInterface\Port\PortInterface::class);
$this->assertSame(Prototype\SinglyImplementedInterface\Adapter\Adapter::class, (string) $alias);
}
}