Switching Themes Programmatically in Drupal 8
Sometimes we need to use different designs for separate pages or for separate menu items in a site. The most obvious way to perform this task is to set separate templates for each case in the current theme and customize CSS and JS for these pages. But there is another way, which is a less complex front-end solution. We can use a different theme.
I used this solution for the first time in Drupal 5. My team worked on an informational resource about the state of Colorado, and each county had a different design. To resolve this discrepancy, we used the Taxonomy Theme module.
You can take a look at the code of that module to get an idea for how to do this in Drupal 5 and Drupal 6. There is a global variable:
global $custom_theme;
We can overwrite its value according to our business logic, and the theme will be automatically switched to ours.
In Drupal 7, there is a hook that allows us to manipulate the theme choice programmatically. There is also a ‘theme callback’ parameter in hook_menu() that allows us to set a separate theme on separate menu items in Drupal 7.
But in the case of Drupal 8, there is a better approach. Let’s try it out!
We’ll apply this to a landing page, so we will start with a Drupal 8 module called Landing Page Constructor. When this module is enabled in Drupal, a new content type called “landingpage” appears. This custom content type should be shown when using the internal theme of the module (Landingpage Service theme), so we need to switch the active theme in Drupal 8 programmatically .
As I mentioned above, Drupal 8 introduces some significant API changes. In fact, we have significant architectural changes! What can we find out about our task? Let’s take a look on https://www.drupal.org/node/2158619.
In Drupal, ‘theme callback’ and hook_custom_theme() are replaced by theme negotiators. So we need to define a service and a service class.
If you work with Drupal 8 as a developer, I have one main piece of advice: use the Drupal Console (drupalconsole.com) for all common routine tasks. We will use Drupal Console to resolve our task.
$ drupal generate:service
This command provides a short dialog. In our case, we don’t need to set a custom interface for our class. So we need to answer “no” to the following question in the dialog:
Create an interface (yes/no) [yes]:
Three files will be generated at the end of this process. In my case, they are:
landingpage.services.yml
src/LandingpageThemeNegotiator.php
The first file (landingpage.services.yml) looks like this:
services:
landingpage.theme:
class: Drupal\landingpage\LandingpageThemeNegotiator
arguments: []
The second one (LandingpageThemeNegotiator.php) looks like this:
<?php
namespace Drupal\landingpage;
/**
* Class LandingpageThemeNegotiator.
*
* @package Drupal\landingpage
*/
class LandingpageThemeNegotiator {
/**
* Constructor.
*/
public function __construct() {
}
}
Note: it is recommended to put LandingpageThemeNegotiator.php in the /src/Theme folder instead of the /src folder. Then, according to the Drupal documentation at https://www.drupal.org/node/2156625 and https://www.drupal.org/node/2158619, the final view of landingpage.services.yml file will look like this:
services:
landingpage.theme:
class: Drupal\landingpage\Theme\LandingpageThemeNegotiator
arguments: []
tags:
- { name: theme_negotiator, priority: 10 }
Note that we moved the LandingpageThemeNegotiator file from /src to /src/Theme
Drupal\landingpage\Theme\LandingpageThemeNegotiator
Let’s align LandingpageThemeNegotiator.php according to https://www.drupal.org/node/2158619:
<?php
namespace Drupal\landingpage\Theme;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
/**
* Class LandingpageThemeNegotiator.
*
* @package Drupal\landingpage
*/
class LandingpageThemeNegotiator implements ThemeNegotiatorInterface {
/**
* {@inheritdoc}
*/
public function applies(RouteMatchInterface $route_match) {
if (...) {
return TRUE;
}
return FALSE;
}
/**
* {@inheritdoc}
*/
public function determineActiveTheme(RouteMatchInterface $route_match) {
return 'landingpage_service';
}
}
We can use one of two methods: Method “applies” determines if our Theme Negotiator applies in the current situation, and method ‘determineActiveTheme’ returns the active theme that Drupal should set. In this case it’s my custom ‘landingpage_service’ theme.
Now we need to manage how we can to find out if we are on the ‘landingpage’ node page. We have a RouteMatchInterface object, and we can use it to provide the information that we need here. Take a look at the public function RouteMatchInterface::getParameter that returns the requested parameter string/array/object or NULL if there is no requested parameter.
Finally our ‘applies’ method will be:
public function applies(RouteMatchInterface $route_match) {
$node = $route_match->getParameter('node');
if (!empty($node) && $node->bundle() == 'landingpage') {
return TRUE;
}
return false;
}
I hope that this quick tutorial will help you to manipulate Drupal themes programmatically, and also provide a bit of Drupal 8 architecture understanding.