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

Skip to content

Commit 040df3d

Browse files
[DI] Add and wire ServiceSubscriberInterface
1 parent 3d67aba commit 040df3d

File tree

9 files changed

+526
-0
lines changed

9 files changed

+526
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class UnusedTagsPass implements CompilerPassInterface
3333
'kernel.event_listener',
3434
'kernel.event_subscriber',
3535
'kernel.fragment_renderer',
36+
'kernel.service_subscriber',
3637
'monolog.logger',
3738
'routing.expression_language_provider',
3839
'routing.loader',

src/Symfony/Component/DependencyInjection/Compiler/PassConfig.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public function __construct()
5454
new ResolveFactoryClassPass(),
5555
new FactoryReturnTypePass($resolveClassPass),
5656
new CheckDefinitionValidityPass(),
57+
new RegisterServiceSubscribersPass(),
5758
new ResolveNamedArgumentsPass(),
5859
new AutowirePass(),
5960
new ResolveReferencesToAliasesPass(),
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
15+
use Symfony\Component\DependencyInjection\ContainerInterface;
16+
use Symfony\Component\DependencyInjection\Definition;
17+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
18+
use Symfony\Component\DependencyInjection\Reference;
19+
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
20+
use Symfony\Component\DependencyInjection\ServiceLocator;
21+
use Symfony\Component\DependencyInjection\TypedReference;
22+
23+
/**
24+
* Compiler pass to register tagged services that require a service locator.
25+
*
26+
* @author Nicolas Grekas <[email protected]>
27+
*
28+
* @experimental in version 3.3
29+
*/
30+
class RegisterServiceSubscribersPass extends AbstractRecursivePass
31+
{
32+
private $serviceLocator;
33+
34+
protected function processValue($value, $isRoot = false)
35+
{
36+
if ($value instanceof Reference && $this->serviceLocator && 'service_container' === (string) $value) {
37+
return new Reference($this->serviceLocator);
38+
}
39+
40+
if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('kernel.service_subscriber')) {
41+
return parent::processValue($value, $isRoot);
42+
}
43+
44+
$serviceMap = array();
45+
46+
foreach ($value->getTag('kernel.service_subscriber') as $attributes) {
47+
if (!$attributes) {
48+
continue;
49+
}
50+
ksort($attributes);
51+
if (array('key', 'service') !== array_keys(array_filter($attributes))) {
52+
throw new InvalidArgumentException(sprintf('A "kernel.service_subscriber" tag must have either zero or exactly two non-empty "key" and "service" attributes, "%s" given for service "%s".', implode('", "'), $this->currentId));
53+
}
54+
if (isset($serviceMap[$attributes['key']])) {
55+
continue;
56+
}
57+
$serviceMap[$attributes['key']] = new Reference($attributes['service']);
58+
}
59+
$class = $value->getClass();
60+
61+
if (!is_subclass_of($class, ServiceSubscriberInterface::class)) {
62+
if (!class_exists($class, false)) {
63+
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId));
64+
}
65+
66+
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class));
67+
}
68+
$this->container->addObjectResource($class);
69+
$subscriberMap = array();
70+
71+
foreach ($class::getSubscribedServices() as $key => $type) {
72+
if (!is_string($type) || !preg_match('/^\??[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+$/', $type)) {
73+
throw new InvalidArgumentException(sprintf('%s::getSubscribedServices() must return valid PHP types for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, is_string($type) ? $type : gettype($type)));
74+
}
75+
if ($optionalBehavior = '?' === $type[0]) {
76+
$type = substr($type, 1);
77+
$optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE;
78+
}
79+
if (is_int($key)) {
80+
$key = $type;
81+
}
82+
if (!isset($serviceMap[$key])) {
83+
$serviceMap[$key] = new Reference($type);
84+
}
85+
86+
$subscriberMap[$key] = new ServiceClosureArgument(new TypedReference((string) $serviceMap[$key], $type, $optionalBehavior ?: ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE));
87+
unset($serviceMap[$key]);
88+
}
89+
90+
if ($serviceMap = array_keys($serviceMap)) {
91+
$this->container->log($this, sprintf('Service keys "%s" do not exist in the map returned by %s::getSubscribedServices() for service "%s".', implode('", "', $serviceMap), $class, $this->currentId));
92+
}
93+
94+
$serviceLocator = $this->serviceLocator;
95+
$this->serviceLocator = 'service_locator.'.$this->currentId.spl_object_hash($value);
96+
$this->container->register($this->serviceLocator, ServiceLocator::class)->addArgument($subscriberMap)->setPublic(false)->setAutowired($value->isAutowired());
97+
98+
try {
99+
return parent::processValue($value);
100+
} finally {
101+
$this->serviceLocator = $serviceLocator;
102+
}
103+
}
104+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
/**
15+
* A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
16+
*
17+
* It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
18+
* This interface does not dictate any injection method for these service locators, although constructor
19+
* injection is recommended. In the list returned by {@link getSubscribedServices} method, service types
20+
* that start with an interrogation mark "?" are optional, while the other ones are mandatory service
21+
* dependencies. The injected service locators SHOULD NOT allow access to any other services not specified
22+
* by the method.
23+
*
24+
* @author Nicolas Grekas <[email protected]>
25+
*
26+
* @experimental in version 3.3
27+
*/
28+
interface ServiceSubscriberInterface
29+
{
30+
/**
31+
* Returns an array of service types required by such instances, optionally keyed by the service names used internally.
32+
*
33+
* For mandatory dependencies:
34+
*
35+
* * array('logger' => 'Psr\Log\LoggerInterface') means the objects use the "logger" name
36+
* internally to fetch a service which must implement Psr\Log\LoggerInterface.
37+
* * array('Psr\Log\LoggerInterface') is a shortcut for
38+
* * array('Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface')
39+
*
40+
* otherwise:
41+
*
42+
* * array('logger' => '?Psr\Log\LoggerInterface') denotes an optional dependency
43+
* * array('?Psr\Log\LoggerInterface') is a shortcut for
44+
* * array('Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface')
45+
*
46+
* @return array The required service types, optionally keyed by service names
47+
*/
48+
public static function getSubscribedServices();
49+
}

src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,4 +651,24 @@ public function testServiceLocator()
651651
$suffix = PHP_VERSION_ID >= 70100 ? '71' : (PHP_VERSION_ID >= 70000 ? '70' : '55');
652652
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_locator_php'.$suffix.'.php', $dumper->dump());
653653
}
654+
655+
public function testServiceSubscriber()
656+
{
657+
$container = new ContainerBuilder();
658+
$container->register('foo_service', 'TestServiceSubscriber')
659+
->setAutowired(true)
660+
->addArgument(new Reference('service_container'))
661+
->addTag('kernel.service_subscriber', array(
662+
'key' => 'test',
663+
'service' => 'TestServiceSubscriber',
664+
))
665+
;
666+
$container->register('TestServiceSubscriber', 'TestServiceSubscriber');
667+
$container->compile();
668+
669+
$dumper = new PhpDumper($container);
670+
671+
$suffix = PHP_VERSION_ID >= 70100 ? '71' : (PHP_VERSION_ID >= 70000 ? '70' : '55');
672+
$this->assertStringEqualsFile(self::$fixturesPath.'/php/services_subscriber_php'.$suffix.'.php', $dumper->dump());
673+
}
654674
}

src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/classes.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
use Symfony\Component\DependencyInjection\Definition;
44
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper;
5+
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
56

67
function sc_configure($instance)
78
{
@@ -107,3 +108,20 @@ public function __construct($lazyValues)
107108
$this->lazyValues = $lazyValues;
108109
}
109110
}
111+
112+
class TestServiceSubscriber implements ServiceSubscriberInterface
113+
{
114+
public function __construct($container)
115+
{
116+
}
117+
118+
public static function getSubscribedServices()
119+
{
120+
return array(
121+
__CLASS__,
122+
'?stdClass',
123+
'bar' => 'stdClass',
124+
'baz' => '?stdClass',
125+
);
126+
}
127+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
4+
use Symfony\Component\DependencyInjection\ContainerInterface;
5+
use Symfony\Component\DependencyInjection\Container;
6+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
7+
use Symfony\Component\DependencyInjection\Exception\LogicException;
8+
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
9+
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;
10+
use Symfony\Component\DependencyInjection\ServiceLocator;
11+
12+
/**
13+
* ProjectServiceContainer.
14+
*
15+
* This class has been auto-generated
16+
* by the Symfony Dependency Injection Component.
17+
*
18+
* @final since Symfony 3.3
19+
*/
20+
class ProjectServiceContainer extends Container
21+
{
22+
private $parameters;
23+
private $targetDirs = array();
24+
25+
/**
26+
* Constructor.
27+
*/
28+
public function __construct()
29+
{
30+
$this->services = array();
31+
$this->normalizedIds = array(
32+
'autowired.stdclass' => 'autowired.stdClass',
33+
'psr\\container\\containerinterface' => 'Psr\\Container\\ContainerInterface',
34+
'stdclass' => 'stdClass',
35+
'symfony\\component\\dependencyinjection\\container' => 'Symfony\\Component\\DependencyInjection\\Container',
36+
'symfony\\component\\dependencyinjection\\containerinterface' => 'Symfony\\Component\\DependencyInjection\\ContainerInterface',
37+
'testservicesubscriber' => 'TestServiceSubscriber',
38+
);
39+
$this->methodMap = array(
40+
'TestServiceSubscriber' => 'getTestServiceSubscriberService',
41+
'autowired.stdClass' => 'getAutowired_StdClassService',
42+
'foo_service' => 'getFooServiceService',
43+
);
44+
$this->privates = array(
45+
'autowired.stdClass' => true,
46+
);
47+
48+
$this->aliases = array();
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function compile()
55+
{
56+
throw new LogicException('You cannot compile a dumped frozen container.');
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function isFrozen()
63+
{
64+
return true;
65+
}
66+
67+
/**
68+
* Gets the 'TestServiceSubscriber' service.
69+
*
70+
* This service is shared.
71+
* This method always returns the same instance of the service.
72+
*
73+
* @return \TestServiceSubscriber A TestServiceSubscriber instance
74+
*/
75+
protected function getTestServiceSubscriberService()
76+
{
77+
return $this->services['TestServiceSubscriber'] = new \TestServiceSubscriber();
78+
}
79+
80+
/**
81+
* Gets the 'foo_service' service.
82+
*
83+
* This service is autowired.
84+
*
85+
* @return \TestServiceSubscriber A TestServiceSubscriber instance
86+
*/
87+
protected function getFooServiceService()
88+
{
89+
return $this->services['foo_service'] = new \TestServiceSubscriber(new \Symfony\Component\DependencyInjection\ServiceLocator(array('TestServiceSubscriber' => function () {
90+
return ${($_ = isset($this->services['TestServiceSubscriber']) ? $this->services['TestServiceSubscriber'] : $this->get('TestServiceSubscriber')) && false ?: '_'};
91+
}, 'stdClass' => function () {
92+
return ${($_ = isset($this->services['autowired.stdClass']) ? $this->services['autowired.stdClass'] : $this->getAutowired_StdClassService()) && false ?: '_'};
93+
}, 'bar' => function () {
94+
return ${($_ = isset($this->services['autowired.stdClass']) ? $this->services['autowired.stdClass'] : $this->getAutowired_StdClassService()) && false ?: '_'};
95+
}, 'baz' => function () {
96+
return ${($_ = isset($this->services['autowired.stdClass']) ? $this->services['autowired.stdClass'] : $this->getAutowired_StdClassService()) && false ?: '_'};
97+
})));
98+
}
99+
100+
/**
101+
* Gets the 'autowired.stdClass' service.
102+
*
103+
* This service is autowired.
104+
*
105+
* @return \stdClass A stdClass instance
106+
*/
107+
protected function getAutowired_StdClassService()
108+
{
109+
return $this->services['autowired.stdClass'] = new \stdClass();
110+
}
111+
}

0 commit comments

Comments
 (0)