Issue #2472337 by damiankloip, alexpott, Jo Fitzgerald, dawehner, bojanz, Wim Leers: Provide a lazy alternative to service collectors which just detects service IDs
parent
a8cce923b1
commit
ea67660bde
|
@ -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']
|
||||
|
|
|
@ -10,24 +10,29 @@ 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.
|
||||
* This mechanism allows a service to get multiple processor services or just
|
||||
* their IDs 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.
|
||||
* The service collector 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 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.
|
||||
*
|
||||
* It differs from plugins in that all processors are explicitly registered by
|
||||
* To lazily instantiate services the service ID collector pattern can be used,
|
||||
* but the consumer service needs to also inject the 'class_resolver' service.
|
||||
* As constructor injection is used, processors cannot be added at runtime via
|
||||
* this method. However, a consuming service could have setter methods to allow
|
||||
* runtime additions.
|
||||
*
|
||||
* These differ 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 {
|
||||
|
@ -35,26 +40,34 @@ 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
|
||||
* Finds services tagged with 'service_collector' or 'service_id_collector',
|
||||
* then finds all corresponding tagged services.
|
||||
*
|
||||
* The service collector adds a method call for each to the
|
||||
* consuming/collecting service definition.
|
||||
*
|
||||
* Supported 'service_collector' tag attributes:
|
||||
* The service ID collector will collect an array of service IDs and add them
|
||||
* as a constructor argument.
|
||||
*
|
||||
* Supported 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.
|
||||
* - required: Boolean indicating if at least one handler service is required.
|
||||
* Defaults to FALSE.
|
||||
*
|
||||
* Additional tag attributes supported by 'service_collector' only:
|
||||
* - 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 }
|
||||
* - { name: service_id_collector, tag: theme_negotiator }
|
||||
* @endcode
|
||||
*
|
||||
* Supported handler tag attributes:
|
||||
|
@ -75,93 +88,147 @@ class TaggedHandlersPass implements CompilerPassInterface {
|
|||
* If at least one tagged service is required but none are found.
|
||||
*/
|
||||
public function process(ContainerBuilder $container) {
|
||||
foreach ($container->findTaggedServiceIds('service_collector') as $consumer_id => $passes) {
|
||||
foreach ($passes as $pass) {
|
||||
$interface = NULL;
|
||||
$tag = isset($pass['tag']) ? $pass['tag'] : $consumer_id;
|
||||
$method_name = isset($pass['call']) ? $pass['call'] : 'addHandler';
|
||||
$required = isset($pass['required']) ? $pass['required'] : FALSE;
|
||||
|
||||
// Determine parameters.
|
||||
$consumer = $container->getDefinition($consumer_id);
|
||||
$method = new \ReflectionMethod($consumer->getClass(), $method_name);
|
||||
$params = $method->getParameters();
|
||||
|
||||
$interface_pos = 0;
|
||||
$id_pos = NULL;
|
||||
$priority_pos = NULL;
|
||||
$extra_params = [];
|
||||
foreach ($params as $pos => $param) {
|
||||
if ($param->getClass()) {
|
||||
$interface = $param->getClass();
|
||||
}
|
||||
elseif ($param->getName() === 'id') {
|
||||
$id_pos = $pos;
|
||||
}
|
||||
elseif ($param->getName() === 'priority') {
|
||||
$priority_pos = $pos;
|
||||
}
|
||||
else {
|
||||
$extra_params[$param->getName()] = $pos;
|
||||
}
|
||||
// Avoid using ContainerBuilder::findTaggedServiceIds() as that we result in
|
||||
// additional iterations around all the service definitions.
|
||||
foreach ($container->getDefinitions() as $consumer_id => $definition) {
|
||||
$tags = $definition->getTags();
|
||||
if (isset($tags['service_collector'])) {
|
||||
foreach ($tags['service_collector'] as $pass) {
|
||||
$this->processServiceCollectorPass($pass, $consumer_id, $container);
|
||||
}
|
||||
// Determine the ID.
|
||||
|
||||
if (!isset($interface)) {
|
||||
throw new LogicException(vsprintf("Service consumer '%s' class method %s::%s() has to type-hint an interface.", [
|
||||
$consumer_id,
|
||||
$consumer->getClass(),
|
||||
$method_name,
|
||||
]));
|
||||
}
|
||||
$interface = $interface->getName();
|
||||
|
||||
// Find all tagged handlers.
|
||||
$handlers = [];
|
||||
$extra_arguments = [];
|
||||
foreach ($container->findTaggedServiceIds($tag) as $id => $attributes) {
|
||||
// Validate the interface.
|
||||
$handler = $container->getDefinition($id);
|
||||
if (!is_subclass_of($handler->getClass(), $interface)) {
|
||||
throw new LogicException("Service '$id' for consumer '$consumer_id' does not implement $interface.");
|
||||
}
|
||||
$handlers[$id] = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
||||
// Keep track of other tagged handlers arguments.
|
||||
foreach ($extra_params as $name => $pos) {
|
||||
$extra_arguments[$id][$pos] = isset($attributes[0][$name]) ? $attributes[0][$name] : $params[$pos]->getDefaultValue();
|
||||
}
|
||||
}
|
||||
if (empty($handlers)) {
|
||||
if ($required) {
|
||||
throw new LogicException(sprintf("At least one service tagged with '%s' is required.", $tag));
|
||||
}
|
||||
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) {
|
||||
$arguments = [];
|
||||
$arguments[$interface_pos] = new Reference($id);
|
||||
if (isset($priority_pos)) {
|
||||
$arguments[$priority_pos] = $priority;
|
||||
}
|
||||
if (isset($id_pos)) {
|
||||
$arguments[$id_pos] = $id;
|
||||
}
|
||||
// Add in extra arguments.
|
||||
if (isset($extra_arguments[$id])) {
|
||||
// Place extra arguments in their right positions.
|
||||
$arguments += $extra_arguments[$id];
|
||||
}
|
||||
// Sort the arguments by position.
|
||||
ksort($arguments);
|
||||
$consumer->addMethodCall($method_name, $arguments);
|
||||
}
|
||||
if (isset($tags['service_id_collector'])) {
|
||||
foreach ($tags['service_id_collector'] as $pass) {
|
||||
$this->processServiceIdCollectorPass($pass, $consumer_id, $container);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a service collector service pass.
|
||||
*
|
||||
* @param array $pass
|
||||
* 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;
|
||||
$method_name = isset($pass['call']) ? $pass['call'] : 'addHandler';
|
||||
$required = isset($pass['required']) ? $pass['required'] : FALSE;
|
||||
|
||||
// Determine parameters.
|
||||
$consumer = $container->getDefinition($consumer_id);
|
||||
$method = new \ReflectionMethod($consumer->getClass(), $method_name);
|
||||
$params = $method->getParameters();
|
||||
|
||||
$interface_pos = 0;
|
||||
$id_pos = NULL;
|
||||
$priority_pos = NULL;
|
||||
$extra_params = [];
|
||||
foreach ($params as $pos => $param) {
|
||||
if ($param->getClass()) {
|
||||
$interface = $param->getClass();
|
||||
}
|
||||
elseif ($param->getName() === 'id') {
|
||||
$id_pos = $pos;
|
||||
}
|
||||
elseif ($param->getName() === 'priority') {
|
||||
$priority_pos = $pos;
|
||||
}
|
||||
else {
|
||||
$extra_params[$param->getName()] = $pos;
|
||||
}
|
||||
}
|
||||
// Determine the ID.
|
||||
|
||||
if (!isset($interface)) {
|
||||
throw new LogicException(vsprintf("Service consumer '%s' class method %s::%s() has to type-hint an interface.", [
|
||||
$consumer_id,
|
||||
$consumer->getClass(),
|
||||
$method_name,
|
||||
]));
|
||||
}
|
||||
$interface = $interface->getName();
|
||||
|
||||
// Find all tagged handlers.
|
||||
$handlers = [];
|
||||
$extra_arguments = [];
|
||||
foreach ($container->findTaggedServiceIds($tag) as $id => $attributes) {
|
||||
// Validate the interface.
|
||||
$handler = $container->getDefinition($id);
|
||||
if (!is_subclass_of($handler->getClass(), $interface)) {
|
||||
throw new LogicException("Service '$id' for consumer '$consumer_id' does not implement $interface.");
|
||||
}
|
||||
$handlers[$id] = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
||||
// Keep track of other tagged handlers arguments.
|
||||
foreach ($extra_params as $name => $pos) {
|
||||
$extra_arguments[$id][$pos] = isset($attributes[0][$name]) ? $attributes[0][$name] : $params[$pos]->getDefaultValue();
|
||||
}
|
||||
}
|
||||
|
||||
if ($required && empty($handlers)) {
|
||||
throw new LogicException(sprintf("At least one service tagged with '%s' is required.", $tag));
|
||||
}
|
||||
|
||||
// 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) {
|
||||
$arguments = [];
|
||||
$arguments[$interface_pos] = new Reference($id);
|
||||
if (isset($priority_pos)) {
|
||||
$arguments[$priority_pos] = $priority;
|
||||
}
|
||||
if (isset($id_pos)) {
|
||||
$arguments[$id_pos] = $id;
|
||||
}
|
||||
// Add in extra arguments.
|
||||
if (isset($extra_arguments[$id])) {
|
||||
// Place extra arguments in their right positions.
|
||||
$arguments += $extra_arguments[$id];
|
||||
}
|
||||
// Sort the arguments by position.
|
||||
ksort($arguments);
|
||||
$consumer->addMethodCall($method_name, $arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a service collector ID service pass.
|
||||
*
|
||||
* @param array $pass
|
||||
* 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;
|
||||
$required = isset($pass['required']) ? $pass['required'] : FALSE;
|
||||
|
||||
$consumer = $container->getDefinition($consumer_id);
|
||||
|
||||
// Find all tagged handlers.
|
||||
$handlers = [];
|
||||
foreach ($container->findTaggedServiceIds($tag) as $id => $attributes) {
|
||||
$handlers[$id] = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
||||
}
|
||||
|
||||
if ($required && empty($handlers)) {
|
||||
throw new LogicException(sprintf("At least one service tagged with '%s' is required.", $tag));
|
||||
}
|
||||
|
||||
// Sort all handlers by priority.
|
||||
arsort($handlers, SORT_NUMERIC);
|
||||
|
||||
$consumer->addArgument(array_keys($handlers));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\Core\Theme;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ClassResolverInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
|
@ -13,21 +14,12 @@ use Drupal\Core\Routing\RouteMatchInterface;
|
|||
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.
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $sortedNegotiators;
|
||||
|
||||
/**
|
||||
* The access checker for themes.
|
||||
*
|
||||
|
@ -35,48 +27,27 @@ class ThemeNegotiator implements ThemeNegotiatorInterface {
|
|||
*/
|
||||
protected $themeAccess;
|
||||
|
||||
/**
|
||||
* The class resolver.
|
||||
*
|
||||
* @var \Drupal\Core\DependencyInjection\ClassResolverInterface
|
||||
*/
|
||||
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 @@ class ThemeNegotiator implements ThemeNegotiatorInterface {
|
|||
* {@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 @@ class TaggedHandlersPassTest extends UnitTestCase {
|
|||
$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 @@ class TaggedHandlersPassTest extends UnitTestCase {
|
|||
$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 @@ class TaggedHandlersPassTest extends UnitTestCase {
|
|||
$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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
->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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
* @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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
->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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
$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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
* @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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
->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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
->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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
->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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
* @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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
->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 @@ class ThemeNegotiatorTest extends UnitTestCase {
|
|||
->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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue