Commit ea67660b authored by effulgentsia's avatar effulgentsia

Issue #2472337 by damiankloip, alexpott, Jo Fitzgerald, dawehner, bojanz, Wim...

Issue #2472337 by damiankloip, alexpott, Jo Fitzgerald, dawehner, bojanz, Wim Leers: Provide a lazy alternative to service collectors which just detects service IDs
parent a8cce923
......@@ -471,9 +471,9 @@ services:
arguments: ['@app.root', '@module_handler', '@cache.discovery']
theme.negotiator:
class: Drupal\Core\Theme\ThemeNegotiator
arguments: ['@access_check.theme']
arguments: ['@access_check.theme', '@class_resolver']
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']
......
......@@ -2,6 +2,7 @@
namespace Drupal\Core\Theme;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
......@@ -13,70 +14,40 @@
class ThemeNegotiator implements ThemeNegotiatorInterface {
/**
* Holds arrays of theme negotiators, keyed by priority.
* Holds an array of theme negotiator IDs, sorted by priority.
*
* @var array
* @var string[]
*/
protected $negotiators = [];
/**
* Holds the array of theme negotiators sorted by priority.
*
* Set to NULL if the array needs to be re-calculated.
* The access checker for themes.
*
* @var array|null
* @var \Drupal\Core\Theme\ThemeAccessCheck
*/
protected $sortedNegotiators;
protected $themeAccess;
/**
* The access checker for themes.
* The class resolver.
*
* @var \Drupal\Core\Theme\ThemeAccessCheck
* @var \Drupal\Core\DependencyInjection\ClassResolverInterface
*/
protected $themeAccess;
protected $classResolver;
/**
* Constructs a new ThemeNegotiator.
*
* @param \Drupal\Core\Theme\ThemeAccessCheck $theme_access
* The access checker for themes.
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver.
* @param string[] $negotiators
* An array of negotiator IDs.
*/
public function __construct(ThemeAccessCheck $theme_access) {
public function __construct(ThemeAccessCheck $theme_access, ClassResolverInterface $class_resolver, 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 = [];
foreach ($this->negotiators as $builders) {
$this->sortedNegotiators = array_merge($this->sortedNegotiators, $builders);
}
}
return $this->sortedNegotiators;
$this->negotiators = $negotiators;
$this->classResolver = $class_resolver;
}
/**
......@@ -90,7 +61,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->classResolver->getInstanceFromDefinition($negotiator_id);
if ($negotiator->applies($route_match)) {
$theme = $negotiator->determineActiveTheme($route_match);
if ($theme !== NULL && $this->themeAccess->checkAccess($theme)) {
......
......@@ -60,6 +60,25 @@ public function testProcessRequiredHandlers() {
$handler_pass->process($container);
}
/**
* Tests a required consumer with no handlers.
*
* @covers ::process
* @covers ::processServiceIdCollectorPass
*/
public function testIdCollectorProcessRequiredHandlers() {
$this->setExpectedException(LogicException::class, "At least one service tagged with 'consumer_id' is required.");
$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.
*
......@@ -104,6 +123,32 @@ public function testProcess() {
$this->assertCount(2, $method_calls);
}
/**
* 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.
*
......@@ -135,6 +180,39 @@ public function testProcessPriority() {
$this->assertEquals(0, $method_calls[1][1][1]);
}
/**
* 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.
*
......
......@@ -2,6 +2,8 @@
namespace Drupal\Tests\Core\Theme;
use Drupal\Core\DependencyInjection\ClassResolver;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Routing\RouteMatch;
use Drupal\Core\Theme\ThemeNegotiator;
use Drupal\Tests\UnitTestCase;
......@@ -20,6 +22,13 @@ class ThemeNegotiatorTest extends UnitTestCase {
*/
protected $themeAccessCheck;
/**
* The container builder.
*
* @var \Drupal\Core\DependencyInjection\ContainerBuilder
*/
protected $container;
/**
* The request stack.
*
......@@ -34,11 +43,14 @@ class ThemeNegotiatorTest extends UnitTestCase {
*/
protected $themeNegotiator;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->themeAccessCheck = $this->getMockBuilder('\Drupal\Core\Theme\ThemeAccessCheck')
->disableOriginalConstructor()
->getMock();
$this->themeNegotiator = new ThemeNegotiator($this->themeAccessCheck);
$this->container = new ContainerBuilder();
}
/**
......@@ -55,14 +67,16 @@ public function testDetermineActiveTheme() {
->method('applies')
->will($this->returnValue(TRUE));
$this->themeNegotiator->addNegotiator($negotiator, 0);
$this->container->set('test_negotiator', $negotiator);
$negotiators = ['test_negotiator'];
$this->themeAccessCheck->expects($this->any())
->method('checkAccess')
->will($this->returnValue(TRUE));
$route_match = new RouteMatch('test_route', new Route('/test-route'), [], []);
$theme = $this->themeNegotiator->determineActiveTheme($route_match);
$theme = $this->createThemeNegotiator($negotiators)->determineActiveTheme($route_match);
$this->assertEquals('example_test', $theme);
}
......@@ -73,6 +87,8 @@ public function testDetermineActiveTheme() {
* @see \Drupal\Core\Theme\ThemeNegotiator::determineActiveTheme()
*/
public function testDetermineActiveThemeWithPriority() {
$negotiators = [];
$negotiator = $this->getMock('Drupal\Core\Theme\ThemeNegotiatorInterface');
$negotiator->expects($this->once())
->method('determineActiveTheme')
......@@ -81,7 +97,7 @@ public function testDetermineActiveThemeWithPriority() {
->method('applies')
->will($this->returnValue(TRUE));
$this->themeNegotiator->addNegotiator($negotiator, 10);
$negotiators['test_negotiator_1'] = $negotiator;
$negotiator = $this->getMock('Drupal\Core\Theme\ThemeNegotiatorInterface');
$negotiator->expects($this->never())
......@@ -89,14 +105,18 @@ public function testDetermineActiveThemeWithPriority() {
$negotiator->expects($this->never())
->method('applies');
$this->themeNegotiator->addNegotiator($negotiator, 0);
$negotiators['test_negotiator_2'] = $negotiator;
foreach ($negotiators as $id => $negotiator) {
$this->container->set($id, $negotiator);
}
$this->themeAccessCheck->expects($this->any())
->method('checkAccess')
->will($this->returnValue(TRUE));
$route_match = new RouteMatch('test_route', new Route('/test-route'), [], []);
$theme = $this->themeNegotiator->determineActiveTheme($route_match);
$theme = $this->createThemeNegotiator(array_keys($negotiators))->determineActiveTheme($route_match);
$this->assertEquals('example_test', $theme);
}
......@@ -107,6 +127,8 @@ public function testDetermineActiveThemeWithPriority() {
* @see \Drupal\Core\Theme\ThemeNegotiator::determineActiveTheme()
*/
public function testDetermineActiveThemeWithAccessCheck() {
$negotiators = [];
$negotiator = $this->getMock('Drupal\Core\Theme\ThemeNegotiatorInterface');
$negotiator->expects($this->once())
->method('determineActiveTheme')
......@@ -115,7 +137,7 @@ public function testDetermineActiveThemeWithAccessCheck() {
->method('applies')
->will($this->returnValue(TRUE));
$this->themeNegotiator->addNegotiator($negotiator, 10);
$negotiators['test_negotiator_1'] = $negotiator;
$negotiator = $this->getMock('Drupal\Core\Theme\ThemeNegotiatorInterface');
$negotiator->expects($this->once())
......@@ -125,7 +147,11 @@ public function testDetermineActiveThemeWithAccessCheck() {
->method('applies')
->will($this->returnValue(TRUE));
$this->themeNegotiator->addNegotiator($negotiator, 0);
$negotiators['test_negotiator_2'] = $negotiator;
foreach ($negotiators as $id => $negotiator) {
$this->container->set($id, $negotiator);
}
$this->themeAccessCheck->expects($this->at(0))
->method('checkAccess')
......@@ -138,7 +164,7 @@ public function testDetermineActiveThemeWithAccessCheck() {
->will($this->returnValue(TRUE));
$route_match = new RouteMatch('test_route', new Route('/test-route'), [], []);
$theme = $this->themeNegotiator->determineActiveTheme($route_match);
$theme = $this->createThemeNegotiator(array_keys($negotiators))->determineActiveTheme($route_match);
$this->assertEquals('example_test2', $theme);
}
......@@ -149,6 +175,8 @@ public function testDetermineActiveThemeWithAccessCheck() {
* @see \Drupal\Core\Theme\ThemeNegotiatorInterface
*/
public function testDetermineActiveThemeWithNotApplyingNegotiator() {
$negotiators = [];
$negotiator = $this->getMock('Drupal\Core\Theme\ThemeNegotiatorInterface');
$negotiator->expects($this->never())
->method('determineActiveTheme');
......@@ -156,7 +184,7 @@ public function testDetermineActiveThemeWithNotApplyingNegotiator() {
->method('applies')
->will($this->returnValue(FALSE));
$this->themeNegotiator->addNegotiator($negotiator, 10);
$negotiators['test_negotiator_1'] = $negotiator;
$negotiator = $this->getMock('Drupal\Core\Theme\ThemeNegotiatorInterface');
$negotiator->expects($this->once())
......@@ -166,16 +194,35 @@ public function testDetermineActiveThemeWithNotApplyingNegotiator() {
->method('applies')
->will($this->returnValue(TRUE));
$this->themeNegotiator->addNegotiator($negotiator, 0);
$negotiators['test_negotiator_2'] = $negotiator;
foreach ($negotiators as $id => $negotiator) {
$this->container->set($id, $negotiator);
}
$this->themeAccessCheck->expects($this->any())
->method('checkAccess')
->will($this->returnValue(TRUE));
$route_match = new RouteMatch('test_route', new Route('/test-route'), [], []);
$theme = $this->themeNegotiator->determineActiveTheme($route_match);
$theme = $this->createThemeNegotiator(array_keys($negotiators))->determineActiveTheme($route_match);
$this->assertEquals('example_test2', $theme);
}
/**
* Creates a new theme negotiator instance.
*
* @param array $negotiators
* An array of negotiator IDs.
*
* @return \Drupal\Core\Theme\ThemeNegotiator
*/
protected function createThemeNegotiator(array $negotiators) {
$resolver = new ClassResolver();
$resolver->setContainer($this->container);
$theme_negotiator = new ThemeNegotiator($this->themeAccessCheck, $resolver, $negotiators);
return $theme_negotiator;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment