8000 [DI] Add and wire ServiceSubscriberInterface · symfony/symfony@6c8f067 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6c8f067

Browse files
[DI] Add and wire ServiceSubscriberInterface
1 parent 6b80137 commit 6c8f067

File tree

4 files changed

+146
-0
lines changed

4 files changed

+146
-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.controller',
3839
'routing.expression_language_provider',

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: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\ServiceLocatorArgument;
15+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
16+
use Symfony\Component\DependencyInjection\Reference;
17+
use Symfony\Component\DependencyInjection\ServiceSubscriberInterface;
18+
use Symfony\Component\DependencyInjection\AutowirableReference;
19+
20+
/**
21+
* Compiler pass to register tagged services that require a service locator.
22+
*
23+
* @author Nicolas Grekas <p@tchwork.com>
24+
*
25+
* @experimental in version 3.3
26+
*/
27+
class RegisterServiceSubscribersPass extends AbstractRecursivePass
28+
{
29+
private $serviceLocator;
30+
31+
protected function processValue($value, $isRoot = false)
32+
{
33+
if ($value instanceof Reference && $this->serviceLocator && 'service_container' === (string) $value) {
34+
return new Reference($this->serviceLocator);
35+
}
36+
37+
if (!$value instanceof Definition || $value->isAbstract() || $value->isSynthetic() || !$value->hasTag('kernel.service_subscriber')) {
38+
return $value;
39+
}
40+
41+
$serviceMap = array();
42+
43+
foreach ($value->getTag($name) as $services) {
44+
foreach ($services as $key => $service) {
45+
if (!isset($service[0]) || '?' === $service) {
46+
throw new InvalidArgumentException(sprintf('A "kernel.service_subscriber" attribute must be a non-empty string for service "%s", key "%s".', $id, $key));
47+
}
48+
if ($isOptional = '?' === $service[0]) {
49+
$service = substr($service, 1);
50+
}
51+
if (is_int($key)) {
52+
$key = $service;
53+
}
54+
$serviceMap[$key] = new Reference($service, $isOptional ? ContainerInterface::IGNORE_ON_INVALID_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
55+
}
56+
}
57+
$class = $value->getClass();
58+
59+
if (!is_subclass_of($class, ServiceSubscriberInterface::class)) {
60+
if (!class_exists($class, false)) {
61+
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $this->currentId));
62+
}
63+
64+
throw new InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $this->currentId, ServiceSubscriberInterface::class));
65+
}
66+
$this->container->addObjectResource($class);
67+
68+
foreach ($class::getSubscribedServices() as $key => $type) {
69+
if (!is_string($type) || !isset($type[0]) || '?' === $type) {
70+
throw new InvalidArgumentException(sprintf('%s::getSubscribedServices() must return types as non-empty strings for service "%s" key "%s", "%s" returned.', $class, $this->currentId, $key, gettype($type)));
71+
}
72+
if ($isOptional = '?' === $type[0]) {
73+
$type = substr($type, 1);
74+
}
75+
if (is_int($key)) {
76+
$key = $type;
77+
}
78+
if (!isset($serviceMap[$key])) {
79+
$serviceMap[$key] = new AutowirableReference($service, $type, $isOptional ? ContainerInterface::IGNORE_ON_INVALID_REFERENCE : ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE);
80+
} elseif ($isRequired) {
81+
$serviceMap[$key] = new Reference((string) $serviceMap[$key]);
82+
}
83+
}
84+
85+
$serviceLocator = $this->serviceLocator;
86+
$this->serviceLocator = 'service_locator.'.$this->currentId.spl_object_hash($value);
87+
$this->container->setDefinition($this->serviceLocator, new ServiceLocatorArgument($serviceMap))->setPublic(false);
88+
89+
try {
90+
return $this->processValue($value);
91+
} finally {
92+
$this->serviceLocator = $serviceLocator;
93+
}
94+
}
95+
}
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 <fabien@symfony.com>
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 <p@tchwork.com>
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+
}

0 commit comments

Comments
 (0)
0