Skip to content

Commit

Permalink
feature #23991 [DI] Improve psr4-based service discovery (alternative…
Browse files Browse the repository at this point in the history
… 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        | symfony/symfony-docs#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 #22397 (comment), 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
  • Loading branch information
nicolas-grekas committed Aug 29, 2017
2 parents 9f60314 + 00d7f6f commit 0096738
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class YamlFileLoader extends FileLoader

private static $prototypeKeywords = array(
'resource' => 'resource',
'namespace' => 'namespace',
'exclude' => 'exclude',
'parent' => 'parent',
'shared' => 'shared',
Expand Down Expand Up @@ -560,12 +561,17 @@ private function parseDefinition($id, $service, $file, array $defaults)
}
}

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);
}
Expand Down Expand Up @@ -804,7 +810,7 @@ private function checkDefinition($id, array $definition, $file)
{
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component1\Dir1;

class Service1
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component1\Dir2;

class Service2
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component1\Dir3;

class Service3
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component2\Dir1;

class Service4
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Component2\Dir2;

class Service5
{

}
Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
services:
dir1:
namespace: Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\
tags: [foo]
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,45 @@ public function testPrototype()
$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();
Expand Down

0 comments on commit 0096738

Please sign in to comment.