Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fa022f0

Browse files
feature #27077 [DependencyInjection] add ServiceSubscriberTrait (kbond)
This PR was squashed before being merged into the 4.2-dev branch (closes #27077). Discussion ---------- [DependencyInjection] add ServiceSubscriberTrait | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #23898 | License | MIT | Doc PR | symfony/symfony-docs#9809 This allows you to easily configure Service Subscribers with the following convention: ```php class MyService implements ServiceSubscriberInterface { use ServiceSubscriberTrait; public function doSomething() { // $this->router() ... } private function router(): RouterInterface { return $this->container->get(__METHOD__); } } ``` This also allows you to create helper traits like `RouterAware`, `LoggerAware` etc... and compose your services with them (*not* using `__METHOD__` in traits because it doesn't behave as expected.). ```php trait LoggerAware { private function logger(): LoggerInterface { return $this->container->get(__CLASS__.'::'.__FUNCTION__); } } ``` ```php trait RouterAware { private function router(): RouterInterface { return $this->container->get(__CLASS__.'::'.__FUNCTION__); } } ``` ```php class MyService implements ServiceSubscriberInterface { use ServiceSubscriberTrait, LoggerAware, RouterAware; public function doSomething() { // $this->router() ... // $this->logger() ... } } ``` Commits ------- 238e793 [DependencyInjection] add ServiceSubscriberTrait
2 parents b51b17d + 238e793 commit fa022f0

10 files changed

+241
-0
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.2.0
5+
-----
6+
7+
* added `ServiceSubscriberTrait`
8+
49
4.1.0
510
-----
611

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection;
13+
14+
use Psr\Container\ContainerInterface;
15+
16+
/**
17+
* Implementation of ServiceSubscriberInterface that determines subscribed services from
18+
* private method return types. Service ids are available as "ClassName::methodName".
19+
*
20+
* @author Kevin Bond <[email protected]>
21+
*/
22+
trait ServiceSubscriberTrait
23+
{
24+
/** @var ContainerInterface */
25+
private $container;
26+
27+
public static function getSubscribedServices(): array
28+
{
29+
static $services;
30+
31+
if (null !== $services) {
32+
return $services;
33+
}
34+
35+
$services = \is_callable(array('parent', __FUNCTION__)) ? parent::getSubscribedServices() : array();
36+
37+
foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
38+
if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
39+
continue;
40+
}
41+
42+
if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) {
43+
$services[self::class.'::'.$method->name] = '?'.$returnType->getName();
44+
}
45+
}
46+
47+
return $services;
48+
}
49+
50+
/**
51+
* @required
52+
*/
53+
public function setContainer(ContainerInterface $container)
54+
{
55+
$this->container = $container;
56+
57+
if (\is_callable(array('parent', __FUNCTION__))) {
58+
return parent::setContainer($container);
59+
}
60+
}
61+
}

src/Symfony/Component/DependencyInjection/Tests/Compiler/RegisterServiceSubscribersPassTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@
2121
use Symfony\Component\DependencyInjection\Reference;
2222
use Symfony\Component\DependencyInjection\ServiceLocator;
2323
use Symfony\Component\DependencyInjection\Tests\Fixtures\CustomDefinition;
24+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition1;
25+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition2;
26+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestDefinition3;
2427
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriber;
28+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberChild;
29+
use Symfony\Component\DependencyInjection\Tests\Fixtures\TestServiceSubscriberParent;
2530
use Symfony\Component\DependencyInjection\TypedReference;
2631

2732
require_once __DIR__.'/../Fixtures/includes/classes.php';
@@ -136,4 +141,29 @@ public function testExtraServiceSubscriber()
136141
$container->register(TestServiceSubscriber::class, TestServiceSubscriber::class);
137142
$container->compile();
138143
}
144+
145+
public function testServiceSubscriberTrait()
146+
{
147+
$container = new ContainerBuilder();
148+
149+
$container->register('foo', TestServiceSubscriberChild::class)
150+
->addMethodCall('setContainer', array(new Reference(PsrContainerInterface::class)))
151+
->addTag('container.service_subscriber')
152+
;
153+
154+
(new RegisterServiceSubscribersPass())->process($container);
155+
(new ResolveServiceSubscribersPass())->process($container);
156+
157+
$foo = $container->getDefinition('foo');
158+
$locator = $container->getDefinition((string) $foo->getMethodCalls()[0][1][0]);
159+
160+
$expected = array(
161+
TestServiceSubscriberChild::class.'::invalidDefinition' => new ServiceClosureArgument(new TypedReference('Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', 'Symfony\Component\DependencyInjection\Tests\Fixtures\InvalidDefinition', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
162+
TestServiceSubscriberChild::class.'::testDefinition2' => new ServiceClosureArgument(new TypedReference(TestDefinition2::class, TestDefinition2::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
163+
TestServiceSubscriberChild::class.'::testDefinition3' => new ServiceClosureArgument(new TypedReference(TestDefinition3::class, TestDefinition3::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
164+
TestServiceSubscriberParent::class.'::testDefinition1' => new ServiceClosureArgument(new TypedReference(TestDefinition1::class, TestDefinition1::class, ContainerInterface::IGNORE_ON_INVALID_REFERENCE)),
165+
);
166+
167+
$this->assertEquals($expected, $container->getDefinition((string) $locator->getFactory()[0])->getArgument(0));
168+
}
139169
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Component\DependencyInjection\Definition;
6+
7+
class TestDefinition1 extends Definition
8+
{
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Component\DependencyInjection\Definition;
6+
7+
class TestDefinition2 extends Definition
8+
{
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Component\DependencyInjection\Definition;
6+
7+
class TestDefinition3 extends Definition
8+
{
9+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Component\DependencyInjection\ServiceSubscriberTrait;
6+
7+
class TestServiceSubscriberChild extends TestServiceSubscriberParent
8+
{
9+
use ServiceSubscriberTrait, TestServiceSubscriberTrait;
10+
11+
private function testDefinition2(): TestDefinition2
12+
{
13+
return $this->container->get(__METHOD__);
14+
}
15+
16+
private function invalidDefinition(): InvalidDefinition
17+
{
18+
return $this->container->get(__METHOD__);
19+
}
20+
21+
private function privateFunction1(): string
22+
{
23+
}
24+
25+
private function privateFunction2(): string
26+
{
27+
}
28+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
6+
use Symfony\Component\DependencyInjection\ServiceSubscriberTrait;
7+
8+
class TestServiceSubscriberParent implements ServiceSubscriberInterface
9+
{
10+
use ServiceSubscriberTrait;
11+
12+
private function testDefinition1(): TestDefinition1
13+
{
14+
return $this->container->get(__METHOD__);
15+
}
16+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Symfony\Component\DependencyInjection\Tests\Fixtures;
4+
5+
trait TestServiceSubscriberTrait
6+
{
7+
private function testDefinition3(): TestDefinition3
8+
{
9+
return $this->container->get(__CLASS__.'::'.__FUNCTION__);
10+
}
11+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\DependencyInjection\Tests;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Psr\Container\ContainerInterface;
16+
use Symfony\Component\DependencyInjection\Container;
17+
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
18+
use Symfony\Component\DependencyInjection\ServiceSubscriberTrait;
19+
20+
class ServiceSubscriberTraitTest extends TestCase
21+
{
22+
public function testMethodsOnParentsAndChildrenAreIgnoredInGetSubscribedServices()
23+
{
24+
$expected = array(TestService::class.'::aService' => '?Symfony\Component\DependencyInjection\Tests\Service2');
25+
26+
$this->assertEquals($expected, ChildTestService::getSubscribedServices());
27+
}
28+
29+
public function testSetContainerIsCalledOnParent()
30+
{
31+
$container = new Container();
32+
33+
$this->assertSame($container, (new TestService())->setContainer($container));
34+
}
35+
}
36+
37+
class ParentTestService
38+
{
39+
public function aParentService(): Service1
40+
{
41+
}
42+
43+
public function setContainer(ContainerInterface $container)
44+
{
45+
return $container;
46+
}
47+
}
48+
49+
class TestService extends ParentTestService implements ServiceSubscriberInterface
50+
{
51+
use ServiceSubscriberTrait;
52+
53+
public function aService(): Service2
54+
{
55+
}
56+
}
57+
58+
class ChildTestService extends TestService
59+
{
60+
public function aChildService(): Service3
61+
{
62+
}
63+
}

0 commit comments

Comments
 (0)