Commit 6592304c authored by catch's avatar catch

Issue #2213319 by sun, dawehner: Create a single Container CompilerPass to...

Issue #2213319 by sun, dawehner: Create a single Container CompilerPass to collect + add handlers to consumer service definitions.
parent cf902f25
......@@ -81,6 +81,7 @@ services:
class: Drupal\Core\Config\ConfigFactory
tags:
- { name: event_subscriber }
- { name: service_collector, tag: 'config.factory.override', call: addOverride }
arguments: ['@config.storage', '@event_dispatcher', '@config.typed']
config.installer:
class: Drupal\Core\Config\ConfigInstaller
......@@ -169,6 +170,8 @@ services:
arguments: ['@path.alias_storage', '@path.alias_whitelist', '@language_manager']
http_client:
class: Drupal\Core\Http\Client
tags:
- { name: service_collector, tag: http_client_subscriber, call: attach }
http_client_simpletest_subscriber:
class: Drupal\Core\Http\Plugin\SimpletestHttpRequestSubscriber
tags:
......@@ -176,6 +179,8 @@ services:
theme.negotiator:
class: Drupal\Core\Theme\ThemeNegotiator
arguments: ['@access_check.theme', '@request_stack']
tags:
- { name: service_collector, tag: theme_negotiator, call: addNegotiator }
theme.negotiator.default:
class: Drupal\Core\Theme\DefaultNegotiator
arguments: ['@config.factory']
......@@ -276,6 +281,8 @@ services:
arguments: ['@language_manager']
calls:
- [initLanguageManager]
tags:
- { name: service_collector, tag: string_translator, call: addTranslator }
database.slave:
class: Drupal\Core\Database\Connection
factory_class: Drupal\Core\Database\Database
......@@ -321,6 +328,8 @@ services:
arguments: ['@router.route_provider']
calls:
- [setFinalMatcher, ['@router.matcher.final_matcher']]
tags:
- { name: service_collector, tag: route_filter, call: addRouteFilter }
url_generator:
class: Drupal\Core\Routing\UrlGenerator
arguments: ['@router.route_provider', '@path_processor_manager', '@route_processor_manager', '@config.factory', '@settings']
......@@ -333,6 +342,8 @@ services:
router.dynamic:
class: Symfony\Cmf\Component\Routing\DynamicRouter
arguments: ['@router.request_context', '@router.matcher', '@url_generator']
tags:
- { name: service_collector, tag: route_enhancer, call: addRouteEnhancer }
router:
class: Symfony\Cmf\Component\Routing\ChainRouter
calls:
......@@ -595,8 +606,13 @@ services:
arguments: [['@exception_controller', execute]]
route_processor_manager:
class: Drupal\Core\RouteProcessor\RouteProcessorManager
tags:
- { name: service_collector, tag: route_processor_outbound, call: addOutbound }
path_processor_manager:
class: Drupal\Core\PathProcessor\PathProcessorManager
tags:
- { name: service_collector, tag: path_processor_inbound, call: addInbound }
- { name: service_collector, tag: path_processor_outbound, call: addOutbound }
path_processor_decode:
class: Drupal\Core\PathProcessor\PathProcessorDecode
tags:
......@@ -648,6 +664,8 @@ services:
breadcrumb:
class: Drupal\Core\Breadcrumb\BreadcrumbManager
arguments: ['@module_handler']
tags:
- { name: service_collector, tag: breadcrumb_builder, call: addBuilder }
token:
class: Drupal\Core\Utility\Token
arguments: ['@module_handler', '@cache.discovery', '@language_manager']
......
<?php
/**
* @file
* Contains \Drupal\Core\Config\ConfigFactoryOverridePass.
*/
namespace Drupal\Core\Config;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds services to the config factory service.
*
* @see \Drupal\Core\Config\ConfigFactory
* @see \Drupal\Core\Config\ConfigFactoryOverrideInterface
*/
class ConfigFactoryOverridePass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
$manager = $container->getDefinition('config.factory');
$services = array();
foreach ($container->findTaggedServiceIds('config.factory.override') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$services[] = array('id' => $id, 'priority' => $priority);
}
usort($services, array($this, 'compareServicePriorities'));
foreach ($services as $service) {
$manager->addMethodCall('addOverride', array(new Reference($service['id'])));
}
}
/**
* Compares services by priority for ordering.
*
* @param array $a
* Service to compare.
* @param array $b
* Service to compare.
*
* @return int
* Relative order of services to be used with usort. Higher priorities come
* first.
*/
private function compareServicePriorities($a, $b) {
if ($a['priority'] == $b['priority']) {
return 0;
}
return ($a['priority'] > $b['priority']) ? -1 : 1;
}
}
......@@ -9,26 +9,17 @@
use Drupal\Core\Cache\CacheContextsPass;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\Config\ConfigFactoryOverridePass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
use Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterPathProcessorsPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterRouteProcessorsPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterRouteFiltersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterRouteEnhancersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterAuthenticationPass;
use Drupal\Core\DependencyInjection\Compiler\RegisterTwigExtensionsPass;
use Drupal\Core\Http\HttpClientSubscriberPass;
use Drupal\Core\Plugin\PluginManagerPass;
use Drupal\Core\Site\Settings;
use Drupal\Core\Theme\ThemeNegotiatorPass;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Definition;
......@@ -62,39 +53,30 @@ public function register(ContainerBuilder $container) {
// service definitions. This pass must come first so that later
// list-building passes are operating on the post-alter services list.
$container->addCompilerPass(new ModifyServiceDefinitionsPass());
$container->addCompilerPass(new RegisterRouteFiltersPass());
// Collect tagged handler services as method calls on consumer services.
$container->addCompilerPass(new TaggedHandlersPass());
// Add a compiler pass for registering event subscribers.
$container->addCompilerPass(new RegisterKernelListenersPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new RegisterAccessChecksPass());
// Add a compiler pass for upcasting route parameters.
$container->addCompilerPass(new RegisterParamConvertersPass());
$container->addCompilerPass(new RegisterRouteEnhancersPass());
// Add a compiler pass for registering services needing destruction.
$container->addCompilerPass(new RegisterServicesForDestructionPass());
// Add the compiler pass that will process the tagged services.
$container->addCompilerPass(new RegisterPathProcessorsPass());
$container->addCompilerPass(new RegisterRouteProcessorsPass());
$container->addCompilerPass(new ListCacheBinsPass());
$container->addCompilerPass(new CacheContextsPass());
// Add the compiler pass for appending string translators.
$container->addCompilerPass(new RegisterStringTranslatorsPass());
// Add the compiler pass that will process the tagged breadcrumb builder
// services.
$container->addCompilerPass(new RegisterBreadcrumbBuilderPass());
// Add the compiler pass that will process the tagged theme negotiator
// service.
$container->addCompilerPass(new ThemeNegotiatorPass());
// Add the compiler pass that will process the tagged config factory
// override services.
$container->addCompilerPass(new ConfigFactoryOverridePass());
// Add the compiler pass that will process tagged authentication services.
$container->addCompilerPass(new RegisterAuthenticationPass());
// Register Twig extensions.
$container->addCompilerPass(new RegisterTwigExtensionsPass());
// Register plugin managers.
$container->addCompilerPass(new PluginManagerPass());
// Register HTTP client subscribers.
$container->addCompilerPass(new HttpClientSubscriberPass());
}
/**
......@@ -127,7 +109,11 @@ public static function registerTwig(ContainerBuilder $container) {
->addMethodCall('addExtension', array(new Definition('Drupal\Core\Template\TwigExtension')))
// @todo Figure out what to do about debugging functions.
// @see http://drupal.org/node/1804998
->addMethodCall('addExtension', array(new Definition('Twig_Extension_Debug')));
->addMethodCall('addExtension', array(new Definition('Twig_Extension_Debug')))
->addTag('service_collector', array(
'tag' => 'twig.extension',
'call' => 'addExtension',
));
}
/**
......
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services to the breadcrumb_builder service.
*/
class RegisterBreadcrumbBuilderPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('breadcrumb')) {
return;
}
$manager = $container->getDefinition('breadcrumb');
if (is_subclass_of($manager->getClass(), 'Drupal\Core\Breadcrumb\ChainBreadcrumbBuilderInterface')) {
foreach ($container->findTaggedServiceIds('breadcrumb_builder') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$manager->addMethodCall('addBuilder', array(new Reference($id), $priority));
}
}
}
}
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterPathProcessorsPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services to the 'path_processor_manager service.
*/
class RegisterPathProcessorsPass implements CompilerPassInterface {
/**
* Adds services tagged 'path_processor_inbound' to the path processor manager.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to process.
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('path_processor_manager')) {
return;
}
$manager = $container->getDefinition('path_processor_manager');
// Add inbound path processors.
foreach ($container->findTaggedServiceIds('path_processor_inbound') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$manager->addMethodCall('addInbound', array(new Reference($id), $priority));
}
// Add outbound path processors.
foreach ($container->findTaggedServiceIds('path_processor_outbound') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$manager->addMethodCall('addOutbound', array(new Reference($id), $priority));
}
}
}
<?php
/**
* @file
* Contains Drupal\Core\DependencyInjection\Compiler\RegisterRouteEnhancersPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Registers route enhancer services with the router.
*/
class RegisterRouteEnhancersPass implements CompilerPassInterface {
/**
* Adds services tagged with "route_enhancer" to the router.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to process.
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('router.dynamic')) {
return;
}
$router = $container->getDefinition('router.dynamic');
foreach ($container->findTaggedServiceIds('route_enhancer') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$router->addMethodCall('addRouteEnhancer', array(new Reference($id), $priority));
}
}
}
<?php
/**
* @file
* Contains Drupal\Core\DependencyInjection\Compiler\RegisterRouteFiltersPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services tagged 'router.matcher' to the matcher service.
*/
class RegisterRouteFiltersPass implements CompilerPassInterface {
/**
* Adds services tagged 'router.matcher' to the matcher service.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to process.
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('router.matcher')) {
return;
}
$matcher = $container->getDefinition('router.matcher');
foreach ($container->findTaggedServiceIds('route_filter') as $id => $attributes) {
$matcher->addMethodCall('addRouteFilter', array(new Reference($id)));
}
}
}
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterRouteProcessorsPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services to the route_processor_manager service.
*/
class RegisterRouteProcessorsPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('route_processor_manager')) {
return;
}
$manager = $container->getDefinition('route_processor_manager');
// Add outbound route processors.
foreach ($container->findTaggedServiceIds('route_processor_outbound') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$manager->addMethodCall('addOutbound', array(new Reference($id), $priority));
}
}
}
<?php
/**
* @file
* Contains Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Adds services tagged 'string_translator' to the string_translation service.
*/
class RegisterStringTranslatorsPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('string_translation')) {
return;
}
$access_manager = $container->getDefinition('string_translation');
foreach ($container->findTaggedServiceIds('string_translator') as $id => $attributes) {
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
$access_manager->addMethodCall('addTranslator', array(new Reference($id), $priority));
}
}
}
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\RegisterTwigExtensionsPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
/**
* Register additional Twig extensions to the Twig service container.
*/
class RegisterTwigExtensionsPass implements CompilerPassInterface {
/**
* Adds services tagged 'twig.extension' to the Twig service container.
*
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container to process.
*/
public function process(ContainerBuilder $container) {
if (!$container->hasDefinition('twig')) {
return;
}
$definition = $container->getDefinition('twig');
foreach ($container->findTaggedServiceIds('twig.extension') as $id => $attributes) {
// We must assume that the class value has been correcly filled,
// even if the service is created by a factory.
$class = $container->getDefinition($id)->getClass();
$refClass = new \ReflectionClass($class);
$interface = 'Twig_ExtensionInterface';
if (!$refClass->implementsInterface($interface)) {
throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface));
}
$definition->addMethodCall('addExtension', array(new Reference($id)));
}
}
}
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Collects services to add/inject them into a consumer service.
*
* This mechanism allows a service to get multiple processor services injected,
* in order to establish an extensible architecture.
*
* It differs from the factory pattern in that processors are not lazily
* instantiated on demand; the consuming service receives instances of all
* registered processors when it is instantiated. Unlike a factory service, the
* consuming service is not ContainerAware.
*
* It differs from plugins in that all processors are explicitly registered by
* service providers (driven by declarative configuration in code); the mere
* availability of a processor (cf. plugin discovery) does not imply that a
* processor ought to be registered and used.
*
* It differs from regular service definition arguments (constructor injection)
* in that a consuming service MAY allow further processors to be added
* dynamically at runtime. This is why the called method (optionally) receives
* the priority of a processor as second argument.
*
* @see \Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass::process()
*/
class TaggedHandlersPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*
* Finds services tagged with 'service_collector', then finds all
* corresponding tagged services and adds a method call for each to the
* consuming/collecting service definition.
*
* Supported 'service_collector' tag attributes:
* - tag: The tag name used by handler services to collect. Defaults to the
* service ID of the consumer.
* - call: The method name to call on the consumer service. Defaults to
* 'addHandler'. The called method receives two arguments:
* - The handler instance as first argument.
* - Optionally the handler's priority as second argument, if the method
* accepts a second parameter and its name is "priority". In any case, all
* handlers registered at compile time are sorted already.
*
* Example (YAML):
* @code
* tags:
* - { name: service_collector, tag: breadcrumb_builder, call: addBuilder }
* @endcode
*
* Supported handler tag attributes:
* - priority: An integer denoting the priority of the handler. Defaults to 0.
*
* Example (YAML):
* @code
* tags:
* - { name: breadcrumb_builder, priority: 100 }
* @endcode
*
* @throws \Symfony\Component\DependencyInjection\Exception\LogicException
* If the method of a consumer service to be called does not type-hint an
* interface.
* @throws \Symfony\Component\DependencyInjection\Exception\LogicException
* If a tagged handler does not implement the required interface.
*/
public function process(ContainerBuilder $container) {
foreach ($container->findTaggedServiceIds('service_collector') as $consumer_id => $passes) {
foreach ($passes as $pass) {
$tag = isset($pass['tag']) ? $pass['tag'] : $consumer_id;
$method_name = isset($pass['call']) ? $pass['call'] : 'addHandler';
// Determine parameters.
$consumer = $container->getDefinition($consumer_id);
$method = new \ReflectionMethod($consumer->getClass(), $method_name);
$params = $method->getParameters();
$interface = $params[0]->getClass();
$accepts_priority = isset($params[1]) && $params[1]->getName() === 'priority';
if (!isset($interface)) {
if ($container->getParameter('kernel.environment') === 'prod') {
continue;
}
throw new LogicException(vsprintf("Service consumer '%s' class method %s::%s() has to type-hint an interface.", array(
$consumer_id,
$consumer->getClass(),
$method_name,
)));
}
$interface = $interface->getName();
// Find all tagged handlers.
$handlers = array();
foreach ($container->findTaggedServiceIds($tag) as $id => $attributes) {
// Validate the interface.
$handler = $container->getDefinition($id);
if (!is_subclass_of($handler->getClass(), $interface)) {
if ($container->getParameter('kernel.environment') === 'prod') {
continue;
}
throw new LogicException("Service '$id' for consumer '$consumer_id' does not implement $interface.");
}
$handlers[$id] = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
}
if (empty($handlers)) {
continue;
}
// Sort all handlers by priority.
arsort($handlers, SORT_NUMERIC);
// Add a method call for each handler to the consumer service
// definition.
foreach ($handlers as $id => $priority) {
if ($accepts_priority) {
$consumer->addMethodCall($method_name, array(new Reference($id), $priority));
}
else {
$consumer->addMethodCall($method_name, array(new Reference($id)));
}
}
}
}
}
}
<?php
/**
* @file
* Contains \Drupal\Core\Http\HttpClientSubscriberPass.
*/
namespace Drupal\Core\Http;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
/**
* Registers 'http_client_subscriber' tagged services as http client subscribers.
*/
class HttpClientSubscriberPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
$http_client = $container->getDefinition('http_client');
foreach (array_keys($container->findTaggedServiceIds('http_client_subscriber')) as $id) {
$http_client->addMethodCall('attach', array(new Reference($id)));
}
}
}
......@@ -57,7 +57,6 @@ class PathProcessorManager implements InboundPathProcessorInterface, OutboundPat
*
* @param \Drupal\Core\PathProcessor\InboundPathProcessorInterface $processor
* The processor object to add.
*
* @param int $priority
* The priority of the processor being added.
*/
......@@ -97,7 +96,6 @@ protected function getInbound() {
*
* @param \Drupal\Core\PathProcessor\OutboundPathProcessorInterface $processor
* The processor object to add.
*
* @param int $priority
* The priority of the processor being added.
*/
......
......@@ -39,7 +39,6 @@ class RouteProcessorManager implements OutboundRouteProcessorInterface {
*
* @param \Drupal\Core\RouteProcessor\OutboundRouteProcessorInterface $processor
* The processor object to add.
*
* @param int $priority
* The priority of the processor being added.
*/
......
<?php
/**
* @file
* Contains \Drupal\Core\Theme\ThemeNegotiatorPass.
*/
namespace Drupal\Core\Theme;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds services to the theme negotiator service.
*
* @see \Drupal\Core\Theme\ThemeNegotiator
* @see \Drupal\Core\Theme\ThemeNegotiatorInterfa