diff --git a/core/core.services.yml b/core/core.services.yml index 4eb462c..53458b2 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -411,8 +411,10 @@ services: theme.negotiator: class: Drupal\Core\Theme\ThemeNegotiator arguments: ['@access_check.theme'] + calls: + - [setContainer, ['@service_container']] tags: - - { name: service_collector, tag: theme_negotiator, call: addNegotiator } + - { name: service_id_collector, tag: theme_negotiator } theme.negotiator.default: class: Drupal\Core\Theme\DefaultNegotiator arguments: ['@config.factory'] diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php index 8b848a3..f52148c 100644 --- a/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php @@ -94,9 +94,14 @@ public function process(ContainerBuilder $container) { } /** + * Processes a service collector service pass. + * * @param array $pass - * @param $consumer_id + * The service collector pass data. + * @param string $consumer_id + * The consumer service ID. * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container + * The service container. */ protected function processServiceCollectorPass(array $pass, $consumer_id, ContainerBuilder $container) { $tag = isset($pass['tag']) ? $pass['tag'] : $consumer_id; @@ -183,9 +188,14 @@ protected function processServiceCollectorPass(array $pass, $consumer_id, Contai } /** + * Processes a service collector ID service pass. + * * @param array $pass - * @param $consumer_id + * The service collector pass data. + * @param string $consumer_id + * The consumer service ID. * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container + * The service container. */ protected function processServiceIdCollectorPass(array $pass, $consumer_id, ContainerBuilder $container) { $tag = isset($pass['tag']) ? $pass['tag'] : $consumer_id; @@ -206,7 +216,7 @@ protected function processServiceIdCollectorPass(array $pass, $consumer_id, Cont // Sort all handlers by priority. arsort($handlers, SORT_NUMERIC); - $consumer->addArgument($handlers); + $consumer->addArgument(array_keys($handlers)); } } diff --git a/core/lib/Drupal/Core/Theme/ThemeNegotiator.php b/core/lib/Drupal/Core/Theme/ThemeNegotiator.php index 8c1ef03..8a8db53 100644 --- a/core/lib/Drupal/Core/Theme/ThemeNegotiator.php +++ b/core/lib/Drupal/Core/Theme/ThemeNegotiator.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Theme; use Drupal\Core\Routing\RouteMatchInterface; +use Symfony\Component\DependencyInjection\ContainerAware; /** * Provides a class which determines the active theme of the page. @@ -15,26 +16,16 @@ * It therefore uses ThemeNegotiatorInterface objects which are passed in * using the 'theme_negotiator' tag. * - * @see \Drupal\Core\Theme\ThemeNegotiatorPass * @see \Drupal\Core\Theme\ThemeNegotiatorInterface */ -class ThemeNegotiator implements ThemeNegotiatorInterface { +class ThemeNegotiator extends ContainerAware implements ThemeNegotiatorInterface { /** - * Holds arrays of theme negotiators, keyed by priority. + * Holds arrays of theme negotiators, sorted by priority. * * @var array */ - protected $negotiators = array(); - - /** - * Holds the array of theme negotiators sorted by priority. - * - * Set to NULL if the array needs to be re-calculated. - * - * @var array|NULL - */ - protected $sortedNegotiators; + protected $negotiators = []; /** * The access checker for themes. @@ -49,42 +40,9 @@ class ThemeNegotiator implements ThemeNegotiatorInterface { * @param \Drupal\Core\Theme\ThemeAccessCheck $theme_access * The access checker for themes. */ - public function __construct(ThemeAccessCheck $theme_access) { + public function __construct(ThemeAccessCheck $theme_access, array $negotiators) { $this->themeAccess = $theme_access; - } - - /** - * Adds a active theme negotiation service. - * - * @param \Drupal\Core\Theme\ThemeNegotiatorInterface $negotiator - * The theme negotiator to add. - * @param int $priority - * Priority of the theme negotiator. - */ - public function addNegotiator(ThemeNegotiatorInterface $negotiator, $priority) { - $this->negotiators[$priority][] = $negotiator; - // Force the negotiators to be re-sorted. - $this->sortedNegotiators = NULL; - } - - /** - * Returns the sorted array of theme negotiators. - * - * @return array|\Drupal\Core\Theme\ThemeNegotiatorInterface[] - * An array of theme negotiator objects. - */ - protected function getSortedNegotiators() { - if (!isset($this->sortedNegotiators)) { - // Sort the negotiators according to priority. - krsort($this->negotiators); - // Merge nested negotiators from $this->negotiators into - // $this->sortedNegotiators. - $this->sortedNegotiators = array(); - foreach ($this->negotiators as $builders) { - $this->sortedNegotiators = array_merge($this->sortedNegotiators, $builders); - } - } - return $this->sortedNegotiators; + $this->negotiators = $negotiators; } /** @@ -98,7 +56,9 @@ public function applies(RouteMatchInterface $route_match) { * {@inheritdoc} */ public function determineActiveTheme(RouteMatchInterface $route_match) { - foreach ($this->getSortedNegotiators() as $negotiator) { + foreach ($this->negotiators as $negotiator_id) { + $negotiator = $this->container->get($negotiator_id); + if ($negotiator->applies($route_match)) { $theme = $negotiator->determineActiveTheme($route_match); if ($theme !== NULL && $this->themeAccess->checkAccess($theme)) { diff --git a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php index 055a907..3918c31 100644 --- a/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php +++ b/core/tests/Drupal/Tests/Core/DependencyInjection/Compiler/TaggedHandlersPassTest.php @@ -61,6 +61,26 @@ public function testProcessRequiredHandlers() { } /** + * Tests a required consumer with no handlers. + * + * @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException + * @expectedExceptionMessage At least one service tagged with 'consumer_id' is required. + * @covers ::process + * @covers ::processServiceIdCollectorPass + */ + public function testIdCollectorProcessRequiredHandlers() { + $container = $this->buildContainer(); + $container + ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer') + ->addTag('service_id_collector', [ + 'required' => TRUE, + ]); + + $handler_pass = new TaggedHandlersPass(); + $handler_pass->process($container); + } + + /** * Tests consumer with missing interface in non-production environment. * * @expectedException \Symfony\Component\DependencyInjection\Exception\LogicException @@ -103,6 +123,32 @@ public function testProcess() { } /** + * Tests one consumer and two handlers with service ID collection. + * + * @covers ::process + */ + public function testserviceIdProcess() { + $container = $this->buildContainer(); + $container + ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer') + ->addTag('service_id_collector'); + + $container + ->register('handler1', __NAMESPACE__ . '\ValidHandler') + ->addTag('consumer_id'); + $container + ->register('handler2', __NAMESPACE__ . '\ValidHandler') + ->addTag('consumer_id'); + + $handler_pass = new TaggedHandlersPass(); + $handler_pass->process($container); + + $arguments = $container->getDefinition('consumer_id')->getArguments(); + $this->assertCount(1, $arguments); + $this->assertCount(2, $arguments[0]); + } + + /** * Tests handler priority sorting. * * @covers ::process @@ -134,6 +180,39 @@ public function testProcessPriority() { } /** + * Tests handler priority sorting for service ID collection. + * + * @covers ::process + */ + public function testserviceIdProcessPriority() { + $container = $this->buildContainer(); + $container + ->register('consumer_id', __NAMESPACE__ . '\ValidConsumer') + ->addTag('service_id_collector'); + + $container + ->register('handler1', __NAMESPACE__ . '\ValidHandler') + ->addTag('consumer_id'); + $container + ->register('handler2', __NAMESPACE__ . '\ValidHandler') + ->addTag('consumer_id', [ + 'priority' => 20, + ]); + $container + ->register('handler3', __NAMESPACE__ . '\ValidHandler') + ->addTag('consumer_id', [ + 'priority' => 10, + ]); + + $handler_pass = new TaggedHandlersPass(); + $handler_pass->process($container); + + $arguments = $container->getDefinition('consumer_id')->getArguments(); + $this->assertCount(1, $arguments); + $this->assertSame(['handler2', 'handler3', 'handler1'], $arguments[0]); + } + + /** * Tests consumer method without priority parameter. * * @covers ::process