Drupal 8's ThemeNegotiatorInterface provides a way for developers to create their own management class that decides which theme should go with which content. Whether it is something simple like controlling the theme for user profile pages, or something more complex like switching themes depending on a user's role, using this interface can make the process simple and very flexible.
You can create more than one implementation. A typical usage pattern would be to add an implementation to a module that requires a certain theme for specific pages, for example administration pages included in a module. More on multiple implementations and how Drupal "chains" these implementations will be discussed later.
Implement The Interface
To start, take a look at the documentation, and create a class that implements the interface.
<?php
/**
* @file
* Contains \Drupal\jcmodule\Theme\ThemeNegotiator
*/
namespace Drupal\jcmodule\Theme;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
class ThemeNegotiator implements ThemeNegotiatorInterface {
ThemeNegotiatorInterface
requires two methods to be implemented
// Whether this theme negotiator should be used to set the theme.
function applies(RouteMatchInterface $route_match)
// Determines the active theme for the request.
function determineActiveTheme(RouteMatchInterface $route_match)
If applies
return true, determineActiveTheme
is called and either the theme is chosen or null is returned. As you can see, both methods have the same signature and return similar values related to a theme switch.
To keep my class easy to maintain, I created my own common negotiateRoute
function that satisfies the requirements for both interface functions.. If my function returns a theme name, that theme is used.
Here is my common class function
/**
* Function that does all of the work
* @param RouteMatchInterface $route_match
* @return null|string
*/
private function negotiateRoute(RouteMatchInterface $route_match)
{
if ($route_match->getRouteName() == 'user.login')
{
return 'seven';
}
elseif ($route_match->getRouteName() == 'some.other.route')
{
return 'some_other_theme';
}
return null;
}
Symfony Service Requirement
For Drupal to find your implementation, you need to set up your class as a Symfony service and tag it.
# Module services file jcmodule.services.yml
services:
jcmodule.theme.negotiator:
class: Drupal\jcmodule\Theme\ThemeNegotiator
tags:
- { name: theme_negotiator, priority: 1000 }
- The tag name
theme_negotiator
tells Drupal that this is a class that implementsThemeNegotiatorInterface
, and should be used to determine the theme in the current request. - The tag priority tells Drupal how important this class is in relation to other
ThemeNegotiatorInterface
implementations in your project. The higher the priority, the sooner Drupal will call your class logic.
How this all works in Drupal
- When is comes time for Drupal to render your page and select a theme, it looks for all classes that implement
ThemeNegotiatorInterface
. - All implementations begin to be called in priority order, from the highest to the lowest.
- If the
applies
method returns false, Drupal moves on to the next implementation. - If the
applies
method returns true, Drupal calls thedetermineActiveTheme
method in the same implementation. - If
determineActiveTheme
returns null, Drupal moves on to the next implementation. - If
determineActiveTheme
returns a theme name, Drupal will use this theme, and no further implementations are called. - Drupal has its own default implementation. If no other implementations selects a theme, the default will select a theme.
In my example, routes are checked to determine the theme, but you can add whatever logic you may need. This is where you can get creative. Switching the theme based on a user's role can be done within this context:
$userRolesArray = \Drupal::currentUser()->getRoles();
if (in_array("administrator", $userRolesArray))
{
return 'seven';
}
Putting it all together
Here is the full source of my ThemeNegotiatorInterface
implementation example on GitHub.
Conclusion
There are many modules in Drupal 7 & 8 that provide rule based tools for switching themes, like Administration Theme and ThemeKey to name just a few. However, few of these modules, at least at the time that this article was published, have been ported yet to Drupal 8. Similar modules can make the process easy to maintain and portable across Drupal instances. But if you don't want to wait for them to be ported to Drupal 8, or have complex rules to implement, or need a solution for your own custom module, ThemeNegotiatorInterface
may be the way to go.
And it's always fun learning how to do it yourself.