feature #23991 [DI] Improve psr4-based service discovery (alternative implementation) (kbond)
This PR was merged into the 3.4 branch.
Discussion
----------
[DI] Improve psr4-based service discovery (alternative implementation)
| Q | A
| ------------- | ---
| Branch? | 3.4
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | #22397
| License | MIT
| Doc PR | https://github.com/symfony/symfony-docs/pull/8310
This is an alternative to #23986. It is simpler and doesn't require a second glob in the service id. This adds a `namespace` option. It is optional and if it isn't used, then it falls back to using the service id (current behaviour).
As stof mentions in https://github.com/symfony/symfony/issues/22397#issuecomment-324972955, it is consistent with the xml loader.
With this feature, you can define your services like this:
```yaml
services:
command_handlers:
namespace: App\Domain\
resource: ../../src/Domain/*/CommandHandler
tags: [command_handler]
event_subscribers:
namespace: App\Domain\
resource: ../../src/Domain/*/EventSubscriber
tags: [event_subscriber]
```
ping @stof, @nicolas-grekas
Commits
-------
00d7f6f
[DI] improve psr4-based service discovery with namespace option
This commit is contained in:
commit
0096738b52
@ -63,6 +63,7 @@ class YamlFileLoader extends FileLoader
|
||||
|
||||
private static $prototypeKeywords = array(
|
||||
'resource' => 'resource',
|
||||
'namespace' => 'namespace',
|
||||
'exclude' => 'exclude',
|
||||
'parent' => 'parent',
|
||||
'shared' => 'shared',
|
||||
@ -560,12 +561,17 @@ class YamlFileLoader extends FileLoader
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('namespace', $service) && !array_key_exists('resource', $service)) {
|
||||
throw new InvalidArgumentException(sprintf('A "resource" attribute must be set when the "namespace" attribute is set for service "%s" in %s. Check your YAML syntax.', $id, $file));
|
||||
}
|
||||
|
||||
if (array_key_exists('resource', $service)) {
|
||||
if (!is_string($service['resource'])) {
|
||||
throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file));
|
||||
}
|
||||
$exclude = isset($service['exclude']) ? $service['exclude'] : null;
|
||||
$this->registerClasses($definition, $id, $service['resource'], $exclude);
|
||||
$namespace = isset($service['namespace']) ? $service['namespace'] : $id;
|
||||
$this->registerClasses($definition, $namespace, $service['resource'], $exclude);
|
||||
} else {
|
||||
$this->setDefinition($id, $definition);
|
||||
}
|
||||
@ -804,7 +810,7 @@ class YamlFileLoader extends FileLoader
|
||||
{
|
||||
if ($throw = $this->isLoadingInstanceof) {
|
||||
$keywords = self::$instanceofKeywords;
|
||||
} elseif ($throw = isset($definition['resource'])) {
|
||||
} elseif ($throw = (isset($definition['resource']) || isset($definition['namespace']))) {
|
||||
$keywords = self::$prototypeKeywords;
|
||||
} else {
|
||||
$keywords = self::$serviceKeywords;
|
||||
|
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component1\Dir1;
|
||||
|
||||
class Service1
|
||||
{
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component1\Dir2;
|
||||
|
||||
class Service2
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component1\Dir3;
|
||||
|
||||
class Service3
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component2\Dir1;
|
||||
|
||||
class Service4
|
||||
{
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component2\Dir2;
|
||||
|
||||
class Service5
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
services:
|
||||
dir1:
|
||||
namespace: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\
|
||||
resource: ../Prototype/OtherDir/*/Dir1
|
||||
tags: [foo]
|
||||
|
||||
dir2:
|
||||
namespace: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\
|
||||
resource: ../Prototype/OtherDir/*/Dir2
|
||||
tags: [bar]
|
@ -0,0 +1,4 @@
|
||||
services:
|
||||
dir1:
|
||||
namespace: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\
|
||||
tags: [foo]
|
@ -390,6 +390,45 @@ class YamlFileLoaderTest extends TestCase
|
||||
$this->assertContains('reflection.Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar', $resources);
|
||||
}
|
||||
|
||||
public function testPrototypeWithNamespace()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
|
||||
$loader->load('services_prototype_namespace.yml');
|
||||
|
||||
$ids = array_keys($container->getDefinitions());
|
||||
sort($ids);
|
||||
|
||||
$this->assertSame(array(
|
||||
Prototype\OtherDir\Component1\Dir1\Service1::class,
|
||||
Prototype\OtherDir\Component1\Dir2\Service2::class,
|
||||
Prototype\OtherDir\Component2\Dir1\Service4::class,
|
||||
Prototype\OtherDir\Component2\Dir2\Service5::class,
|
||||
'service_container',
|
||||
), $ids);
|
||||
|
||||
$this->assertTrue($container->getDefinition(Prototype\OtherDir\Component1\Dir1\Service1::class)->hasTag('foo'));
|
||||
$this->assertTrue($container->getDefinition(Prototype\OtherDir\Component2\Dir1\Service4::class)->hasTag('foo'));
|
||||
$this->assertFalse($container->getDefinition(Prototype\OtherDir\Component1\Dir1\Service1::class)->hasTag('bar'));
|
||||
$this->assertFalse($container->getDefinition(Prototype\OtherDir\Component2\Dir1\Service4::class)->hasTag('bar'));
|
||||
|
||||
$this->assertTrue($container->getDefinition(Prototype\OtherDir\Component1\Dir2\Service2::class)->hasTag('bar'));
|
||||
$this->assertTrue($container->getDefinition(Prototype\OtherDir\Component2\Dir2\Service5::class)->hasTag('bar'));
|
||||
$this->assertFalse($container->getDefinition(Prototype\OtherDir\Component1\Dir2\Service2::class)->hasTag('foo'));
|
||||
$this->assertFalse($container->getDefinition(Prototype\OtherDir\Component2\Dir2\Service5::class)->hasTag('foo'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
|
||||
* @expectedExceptionMessageRegExp /A "resource" attribute must be set when the "namespace" attribute is set for service ".+" in .+/
|
||||
*/
|
||||
public function testPrototypeWithNamespaceAndNoResource()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
$loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml'));
|
||||
$loader->load('services_prototype_namespace_without_resource.yml');
|
||||
}
|
||||
|
||||
public function testDefaults()
|
||||
{
|
||||
$container = new ContainerBuilder();
|
||||
|
Reference in New Issue
Block a user