Hooks explained for dummies

Frontenders don’t like diving in to PHP, but if you do master a few PHP functions, a lot of possibilities will open up.

If you already have a good understanding of the hook system, please skip to: "Extending existing principles".

Trying to understand the complexity of the different functions as a themer is as endless, as it is deep. Researching it, usually ends up with a whole lot of mumbo-jumbo-backendy complex gibberish.

A tale of metaphors #

What follows is a tale of metaphors to smooth out this learning curve:

  1. A browser arrives at the doorstep of your Drupal-site. The browser rings the door and through the intercom demands the lego-castle with the name node/7 (I know, I know, stick with it :) )
  2. Drupal, having prepared for this moment, pulls it’s socks straight, and grabs it’s prioritised todo-list. Exciting, a Lego castle!
  3. A Lego castle is build out of different floors, and there are loads of different types of floors to choose from. Each type of floor has its own blueprint.
  4. On top of the list: make an inventory of all the blueprints.
  5. Alright, will do, Drupal thinks. Drupal walks over to it’s desk and gathers all the blueprints spread out over its desk. Each blueprint has a unique name and some of them have a sticky note attached.
  6. Now that the inventory list is made, Drupal reads its second todo: find the plan for building the castle named “node/7”.
  7. Drupal goes over to its vault, opens it with a code that no-one else knows and walks in. Finding the safety-deposit box labeled “node/7”, Drupal opens it. Inside is a plan to make the lego-castle. Drupal reads the 3rd todo on it’s priority list:
  8. Make the castle. Drupal takes a look at the plan, and finds  an illustration of how the castle will look:
  9. <page>
      <region>
        <node>
          <field>b</field>
          <field>a</field>
        </node>
      </region>
    
      <region>
        <block>
        </block>
      </region>
    </page>
  10. [Plan build instructions #1]: build the ground floor of the castle. Following the <page> blueprint.
  11. [Plan build instructions #2]: After the ground floor, build 2 separate <region> floors on top of the ground floor. The region floors are right next to each other, one in red, and one in orange.
  12. [Plan build instructions #3]: On top of the red <region>, Drupal builds another entire floor, this time following the <node> blueprint.
  13. [Plan build instructions #4]: On top of the <node> floor, Drupal builds 2 separate rooms next to each other, both following the <field> blueprint ,but each in a different color of Lego.
  14. [Plan build instructions #5]: On top of the orange <region> floor, Drupal builds a floor following the <block> blueprint.
  15. Second to last todo: take off the sticky note found on the page-blueprint, and attach it to the <page> floor. Do the same with all the other blueprints respectively.
  16. When the work is done, Drupal opens the door and hands the castle to the browser waiting at its doorstep.

A long story, but one that we can use, to explain all our options as a Frontender in a Drupal-theme.

Template files #

The .html.twig files or the template files are the blueprints of the previous story. By overwriting a template or blueprint, we can change how each floor looks like! We can add detail, or we can remove certain aspects of the original blueprint. We can also decide, where we put the fundamentals for future floors to be built on top of this floor.

Only certain changes are possible, for example you can’t change the order of the floors, because the order is not defined in a blueprint. The order of the floors is defined in the plan, and the plan we can't change by altering individual blueprints.

This means that we can impact the plan to look like something like this:

<page>    
  <region>
    <block></block>
  </region>

  Some random extra text

  <region>
    <node>
      <field></field>
      <field></field>
    </node>
  </region>
</page>

In the example it means that you could overwrite the page.html.twig file and re-order the regions. You could also add an extra label in between the 2 regions. But you can’t decide to print the node directly in a page, skipping the region component.

Twig debugging #

If each template file is a blueprint, then you need an easy way to discover which templates are available to overwrite in your site.

Drupal has a way to turn on this kind of information for Twig. Find out how here: https://befused.com/drupal/twig-debug 

Once enabled you should see something like this in your inspector:

<!-- THEME DEBUG -->
<!-- THEME HOOK: 'node' -->
<!-- FILE NAME SUGGESTIONS:
   * node--page--full.html.twig
   * node--page.html.twig
   * node--full.html.twig
   x node.html.twig
-->
<!-- BEGIN OUTPUT from 'themes/compony/components/node/node.html.twig' -->
  <article>...</article>
<!-- END OUTPUT from 'themes/compony/components/node/node.html.twig' -->

This gives more information then you might think. You can find out where the file lives that Drupal is using: themes/compony/components/node/node.html.twig.

Not only that, you can also see that the name of this blueprint is node:

<!-- THEME HOOK: 'node' --> 

And last but not least, you can see the order of priority of how Drupal will search for template files. It starts on top of the list, and searches downwards, in this example, Drupal is using the node.html.twig, because there is a little x next to it. If a node--page.html.twig would exist, Drupal would only read that one instead of using node.html.twig.

The hook system #

This is going to be the most understandable explanation of Drupal’s hook system you’ll ever read. Think back to the conceptual story of Drupal running around in its apartment, making a Lego castle. You can change each todo of Drupal, by hooking in to it, BUT you can’t change the priority in which Drupal does them.

In terms of the story it means that you can add blueprints when Drupal is making an inventory. Or add color-variations of existing blueprints. Or change some sticky notes on the blueprints. At each point in time, you can use the information of what happened before to influence your next step. You can change blueprints based on who is at the door for example.

But, you can’t reorder steps. You can’t change how the page template will look, depending on what fields will be used, because the field blueprints are only consulted after the page blueprints.

To influence Drupal's todos, you have to write a hook-function. So let’s start with the most common hook explained first: the preprocess hook!

HOOK_preprocess_hook #

The function name HOOK_preprocess_hook is the way Drupal names it's preprocess-functions. This function changes the blueprints of a certain component. in practise you replace the HOOK by the name of your theme, and the last hook, you replace by the component you want to change. This is the part that shows up in <!-- THEME HOOK: 'node' --> when you enable Twig debugging.

This function, ends up looking like this if we choose to preprocess nodes:

node
node.theme

function my_theme_preprocess_node(&$variables) {
   // DO stuff here...
}

The &$variables is Drupal’s way of giving you variables in that function, don’t worry about that for now.

A few chapters ago, we mentioned that we could also change the blueprint of a component by overwriting the template file. So what does this preprocess-function do then?

To understand how this hook works together with the template file, let’s look at how a simplified node.html.twig template could look like:
 

node
Twig Created with Sketch.
node.html.twig

<article {{ attributes }}>
  <h2>
    <a href="{{ url }}" rel="bookmark">{{ label }}</a>
  </h2>

  {{ content }}
</article>

If you see words in between double brackets {{ }}, it means they are Twig variables, Drupal uses these variables to inject dynamic content.

The preprocess hook allows us to change or provide these variables, right before they arrive in the template.

This means that we could play around with the variables Twig is going to use in the template.

node
node.theme

function my_theme_preprocess_node(&$variables) {
  // This will create a new variable to be used by Twig that can be printed by writing {{ party }} and in this case it will print the string ‘jipie’.
  $variables['party'] = 'jipie';

  // This will add an extra class to the {{ attributes }}
  $variables['attributes']['class'][] = 'my-new-shiny-class';

  // this attaches a library with php
  $variables['#attached']['library'][] = 'compony/wysiwyg-content';
}

Twig Created with Sketch.
node.html.twig

<article {{ attributes }}>
  <h2>
    <a href="{{ url }}" rel="bookmark">{{ label }}</a>
  </h2>

  {{ party }}

  {{ content }}
</article>

The 3 changes done inside the hook, you could also do in the template file, without having to write this hook. But it showcases that the hook and the template go hand in hand.

If you can achieve your change in both Twig and PHP, it's recommended to do it in Twig, as long as it stays reasonably readable. In general, more complex changes are a bit more easy and readable in PHP than in Twig.

On to another very powerful function: the theme_suggestions hook!

HOOK_theme_suggestions_hook_alter #

The story

You can follow along by downloading the taxonomy-component.

In the conceptual story, we mentioned that Drupal picked certain colors of the blueprints. What we left out, was that that if Drupal couldn’t find a certain color it would use the default color blueprint. Each color metaphorically stands for a variation that Drupal can pick for a blueprint. In Drupal terms, we call these template suggestions.

When you inspect a component in the browser with twig debugging turned on, we can see something like this:

<!-- FILE NAME SUGGESTIONS:
   * node--page--full.html.twig
   * node--page.html.twig
   * node--full.html.twig
   x node.html.twig
-->

You can see the order of priority of how Drupal will search for template files. It starts on top of the list, and searches downwards, in this example, Drupal is using the node.html.twig, because there is a little x next to it. If a node--page.html.twig would exist, Drupal would only read that one instead of using the node.html.twig.

The HOOK_theme_suggestions_hook_alter function allows us to add more color options for Drupal to consider.

The function

The function HOOK_theme_suggestions_hook_alter in Drupal terms, allows us to add more template suggestions.

In practise you replace the HOOK by the name of your theme, and the last hook you replace by the component you want to change. This is the part that shows up in <-- THEME HOOK: 'node' --> when you enable Twig debugging.

So the function becomes:

node
node.theme

function my_theme_theme_suggestions_node_alter(array &$suggestions, array $variables) {
  $suggestions[] = 'node__party_pooper';
}

Twig Created with Sketch.
node--party-pooper.html.twig
Twig Created with Sketch.
node.html.twig

If you rebuild Drupal’s cache after saving this file, and you inspect your component again, you should now see this in the debugging info:

<!-- FILE NAME SUGGESTIONS:
   * node--party-pooper.html.twig
   * node--page--full.html.twig
   * node--page.html.twig
   * node--full.html.twig
   x node.html.twig
-->

Which means that you could now create a template file named node--party-pooper.html.twig and Drupal will use it instead of using node.html.twig!

The practical use of this function is to create more variations depending on a certain logic, such as the author of the node or any other parameter you can can find in PHP. You could have a template that is only used if the author of that specific node is ‘admin’ for example.

We can now add different colors of a certain blueprint (node in this example), and in the previous chapter we explained how to change a blueprint’s variables with preprocess.

The $variables variable used in the PHP function, is the exact same as we've seen in the HOOK_preprocess_hook function. The $suggestions variable contains all the current suggestions. If you write $suggestions[], it means that you add to the original array of suggestions already in place before this hook

But how about adding a completely new blueprint? That is something a theme hook can do for us!

HOOK_theme__hook #

The story

Let’s use the conceptual story of Drupal’s apartment once again. Before Drupal makes an inventory of blueprints, we can add a completely new blueprint, one we invented ourselves. For Drupal there will be no difference in how this new blueprint is treated.

The function

Like the node blueprint, the new blueprint will have it’s own .html.twig file, it’s own preprocess hook and it's own set of template suggestions. Cool right?

Let’s have an example how that would look:

text-dropdown
text-dropdown.theme

function my_theme_theme__text_dropdown($existing, $type, $theme, $path) {
  return [
    'template' => 'text-dropdown',
    'variables' => [
      'toggle' => NULL,
      'content' => NULL,
      'classes' => NULL,
    ],
  ];
}

Twig Created with Sketch.
text-dropdown.html.twig

{% set attributes = create_attribute() %}

<div{{ attributes.addClass(classes).addClass('text-dropdown') }}>
  <button type="button" class="text-dropdown__toggle">
    <span>{{ toggle }}</span>
  </button>

  <div class="text-dropdown__content">
    <p>{{ content }}</p>
  </div>
</div>

First step is writing a HOOK_theme, in where we replace the word HOOK by the name of our theme and the hook by the name of the component.

Inside 'variables', we can define the different variables that we want Drupal to pass to the template from PHP. You can choose the keys (toggle, content, classes) and their values here at your own choosing.

In our case:

  • toggle will be the text inside the button.
  • content will be the text inside the thing that drops out.
  • classes will be the different HTML-classes we want to be able to add to our component.

The NULL part means that there is no default content for these 3 variables.

The previous hooks that we talked about, you could also use on this newly created component:

text-dropdown
dist
js-badge Created with Sketch.
text-dropdown.js
css3-full Created with Sketch.
text-dropdown.css
text-dropdown.theme

function my_theme_theme__text_dropdown($existing, $type, $theme, $path) {
  return [
    'template' => 'text-dropdown',
    'variables' => [
      'toggle' => NULL,
      'content' => NULL,
      'classes' => NULL,
    ],
  ];
}

function my_theme_preprocess_text_dropdown(&$variables) {
  // This will create a new variable to be used by Twig that can be printed by writing {{ party }} and in this case it will print the string ‘jipie’.
  $variables['party'] = 'jipie';
}

function my_theme_theme_suggestions_text_dropdown_alter(array &$suggestions, array $variables) {
  if($variables['toggle'] == 'read more')
    $suggestions[] = 'text_dropdown__lean';
  }
}

Twig Created with Sketch.
text-dropdown.html.twig

{% set attributes = create_attribute() %}

{{ attach_library('my-theme/text-dropdown') }}

{% block before %}{% endblock %}

<div{{ attributes.addClass(classes).addClass('text-dropdown') }}>
  <button type="button" class="text-dropdown__toggle">
    <span>{{ toggle }}</span>
  </button>

  <div class="text-dropdown__content">
    {% block content %}
      <p>{{ content }}</p>
      {{ party }}
    {% endblock %}
  </div>
</div>

Twig Created with Sketch.
text-dropdown--lean.html.twig

{% extends 'text-dropdown.html.twig' %}

YAML Created with Sketch.
libraries.yml
js-badge Created with Sketch.
text-dropdown.js
sass Created with Sketch.
text-dropdown.scss

Drupal is capable of using this component anywhere it wants, like the pre-existing components that come by default with Drupal.

HOOK_library_info_alter__hook #

The story

Back to the story once more! Remember the sticky notes that were attached to the blueprints? That was a metaphor for attaching libraries to components. You can use this hook to alter which libraries are attached to components.

The function

If you only want to add libraries, you can use the attach_library function. But what if you want to do the opposite? If you want to swap out or delete an already attached library, the HOOK_library_info_alter__hook function comes in handy:

text-dropdown.theme

function my_theme_library_info_alter__text_dropdown(&$libraries, $extension) {
  unset($libraries['jquery.ui']['css']);
}

unset($libraries['jquery.ui']['css']); means that we will strip away the CSS from the jquery.ui library from core. Because for example it's styling is incompatible with the styling we are writing for our text-dropdown component.

To completely remove the jquery.ui library from loading (including the JS) you could for example write unset($libraries['jquery.ui']); But that will likely break functionality of other components .