Dec 12 2018
Dec 12

Using partial Twig templates is a great way to organise your frontend code because you can reuse code fragments in multiple templates. But what happens if you want to use the same partial template in multiple places and customise its content slightly?

You can do this by passing variables to the included partial template using the with keyword.

Here is an example of including a partial Twig template using the with keyword to pass it a variable:

 {% include 'paywall.html.twig' with { 'title': 'Sign in with your membership details to attend this event' } %}

Background: partial twig templates

Partial Twig templates help you reduce code duplication by adding code in a template and including it in multiple other templates. To find out more, check out my tutorial on partial Twig templates.

An example

I recently implemented a paywall for a membership site where people had to log in to view certain articles and book on events. For this to work, there is a box that consists of a login form, links to become a member (if you aren’t already) and some intro text to explain why they need to log in. Everything in this box was the same for articles and events except for the intro text.

All the code for the box is contained in a single partial Twig template, called paywall.html.twig. This template is then included where needed in other templates, such as the node template for articles and events.

The full snippet for paywall.html.php is:

<div class="paywall">
  <h3>Please sign in</h3>
  {{ paywall_login_form }}
  <p><a href="https://befused.com/apply-membership">Apply for membership</a></p>
  <p><a href="https://befused.com/contact">Contact Us</a></p>
</div>

To include this partial template in the node template, I can use the following:

 {% include 'paywall.html.twig' %}

This will add the same markup when I include it in both the event and article node template. In order to customise it, I can use the with keyword to pass in a variable. In this case, I want to customise the title. That way, I can change the title on events and content templates so they are different.

On events node template, I will use the following for the title:

 {% include 'paywall.html.twig' with { 'title': 'Sign in with your membership details to attend this event' } %}

And on the article node template, I can use the following for the title:

 {% include 'paywall.html.twig' with { 'title': 'Sign in with your membership details to continue reading this article' } %}

Then all you need to do is add title as a variable in paywall.html.php.

This code snippet:

<h3>Please sign in</h3>

Will become:

<h3>{{ title }}</h3>

The full snippet for paywall.html.php is:

<div class="paywall">
  <h3>{{ title }}</h3>
  {{ paywall_login_form }}
  <p><a href="https://befused.com/apply-membership">Apply for membership</a></p>
  <p><a href="https://befused.com/contact">Contact Us</a></p>
</div>

When this is rendered, the H3 for articles will become:

<h3>Sign in with your membership details to continue reading this article</h3>

Wrapping up

Using partial templates is a fantastic way to organise your code base and reuse fragments of template code. Using the with keyword takes this one step further by allowing you to customise your partial Twig templates depending on where it is used.

Oct 10 2018
Oct 10

You have a piece of HTML code that needs to be included in multiple Twig templates. The code will be consistent across all of them - if you need to change it, you need to change it everywhere it is used.

As an example, I recently had to include a piece of code from a social share service called RhythmOne. I want to include this code in some, but not all, of the node templates.

A solution

Twig has the ability to add partial templates to another template. To add a partial template, use the include statement and the filename of the template.

Step by step

  1. In your theme, create a directory inside the templates directory called includes
  2. In the includes directory, create a Twig file for your partial template. For example, rhythmone.html.twig' 
  3. In the file you created in step two, add any code you need
  4. Go to the Twig template where you want to include the partial template. For example, a node template
  5. Include the partial template with:
 % include 'rhythmone.html.twig' %

  1. Repeat for any other templates where you need the same partial template

Drupal will automatically look in the template/includes directory in your theme.

Including templates from another theme or module

If you need to include a template from another theme or module, the syntax is slightly different.

If the partial template that you are including is stored anywhere other than the theme you are including it from, Drupal would not know where to find it with:

 {% include 'rhythmone.html.twig' %}

You could add the full path to the template, but that can be long winded.

To get around this, Twig has a concept called namespaces. This is very similar to PHP namespaces in modules. A namespace creates a short cut to the relevant template directory.

Modules and themes in Drupal have Twig namespaces that you can use. This will create short cut to the templates directory inside the relevant module or theme directory.

Let’s look at an example. If you have partial template in a module called mypages, then you can include it in another template in a different module, or theme, with:

 {% include '@mypages/includes/rhythmone.html.twig' %}

The @mypages part of the above code is a Twig namespace. It is made up of:

  • @
  • mypages: the name of the theme or module

The path after the namespace (@mypages) does not need “templates” because Drupal assumes that that is where your templates are stored.

One of the big advantages of using partial templates like this is that if you need to update the code, you only need to change it in one place.

Jul 17 2018
Jul 17

The problem

Sometimes you need to create a page template for a specific content type so that you can customise it. Drupal allows you to create content types for node templates out of the box by following the naming convention node--content-type.html.twig. But things aren’t so simple for page templates. Drupal does not automatically detect page templates for content types purely based on the naming convention. Fortunately it just takes a few lines of code and you can create a page template for any content type you choose.

Before we start, let’s take a brief look at the difference between a node and a page template.

 Page: the template that controls the whole page. This will include a $content variable, which after rendering will be the node.
 Node: the template that controls just the content part of the page.

A solution

To tell Drupal to use the page template for a content type, you can implement hook_theme_suggestions_page_alter(). This hook is used whenever a page.tpl.php template is used and allows you to suggest alternative templates.

In this case, you are going to use hook_theme_suggestions_page_alter() to tell Drupal to use a specific page template for a specific content type, instead of the default page.tpl.php.

Here is an example of how to tell Drupal to use a template for a content type. Add this code to the example.theme file in your theme, and change hook to the name of your theme. If you have already implemented theme_preprocess_page in your theme’s template.php, you can add the 'if' statement to that.

Here is the solution:

/**
 * Implements hook_theme_suggestions_page_alter().
 */
function example_theme_suggestions_page_alter(array &$suggestions, array $variables) {
  if ($node = \Drupal::routeMatch()->getParameter('node')) {
    $suggestions[] = 'page__' . $node->bundle();
  }
}

The solution explained

In this example, you are telling Drupal that you’d like to use a template with the name of the content type when a page is being viewed. Example templates include:

  • page--article.html.twig for a article content type
  • page--gallery.html.twig for a gallery content type

This is added to 'theme_hook_suggestions', which adds it to the pool of potential templates that Drupal can use. In most cases it will use this one, unless you have another template that meets a more specific criteria. For example, you might have a page template for a single node, which would be more specific and used instead. To add a new theme suggestion, implement hook_theme_suggestions_page_alter() in your theme’s .theme file.

Another example, if you have a theme named example, open the file called example.theme (or create one if it doesn’t exist). And then adding the the following function.

function example_theme_suggestions_page_alter(array &$suggestions, array $variables)  

And inside that function, check that you are viewing a node with:

if ($node = \Drupal::routeMatch()->getParameter('node')) {

And finally, add the naming convention to the theme suggestions with:

$suggestions[] = 'page__' . $node->bundle();

$node->bundle(); will return the name of the content type. So the suggested page template for the article content type will be:

page__article

The complete steps

  1. Open example.theme (replace example with your actual theme name)
  2. Add the code snippet below
  3. Replace example in the function name example_theme_suggestions_page_alter with your actual theme name
  4. Refresh the cache

The complete code in example.theme once again is:

/**
 * Implements hook_theme_suggestions_page_alter().
 */
function example_theme_suggestions_page_alter(array &$suggestions, array $variables) {
  if ($node = \Drupal::routeMatch()->getParameter('node')) {
    $suggestions[] = 'page__' . $node->bundle();
  }
}

Jun 28 2018
Jun 28

One of the many changes in Drupal 8 is that all HTML output is rendered via a Twig template. This means that if you want to override the HTML for a given page, node, region or field, you can copy the Twig template that is being used to your theme and make your changes.

As you can see in the diagram below, a page is made up of many templates.

Twig templates on a Drupal page

There are actually more templates than what you see here as this diagram doesn’t go down to field level (each field can have its own template).

For any given page, node, region or field, there is normally more than one template that Drupal could use and it will choose the most specific one. Have a look at Drupal.org to find out more about how Drupal decides which template to use.

So the question is, how do you know which template is being used? And if you override a template, how can you verify that your template is now used?

Fortunately Drupal makes this pretty easy with these steps:

  1. Turn on Twig debug mode
  2. View the page source to see the templates in HTML comments

Turn on Twig debug mode

If you haven’t already done so, you need to create a services.yml file for your site:

  • Go to sites/default
  • Copy default.services.yml and name it services.yml

You’ll notice that undertwig.config, debug is set to false.

To turn on Twig’s debug mode, you can run the following Drupal Console command:

$ drupal site:mode dev

Not using Drupal Console?

You can manually make this change in your sites services.yml file. In the services.yml file, find twig.config and set debug to true.

Don't have twig.config in your services.yml file?
Don't worry, you may not have it in your particular setup. All you need to do is add the following in your services.yml file:

parameters:
  twig.config:
    debug: true

Check that if you already have parameters. You may have another entry such as:

parameters:
  http.response.debug_cacheability_headers: true

If this is the case, add the twig.config and debug lines to that, like so:

parameters:
  http.response.debug_cacheability_headers: true
  twig.config:
    debug: true

Once you have this set up, it is time to view the page source to see the Twig templates.

View the page source

Go to a node and view the page source. You should now see a whole range of HTML comments that list the Twig templates like so:

<!— THEME DEBUG -->
<!-- THEME HOOK: 'html' -->
<!-- FILE NAME SUGGESTIONS:
html--front.html.twig
html--node.html.twig
   x html.html.twig
-->
<!-- BEGIN OUTPUT from 'core/themes/classy/templates/layout/html.html.twig' -->

What is this telling you?

For each template that is used for the page, these comments are telling you the suggested templates. You’ll see a x next to the template that is used. And then the BEGIN OUTPUT line is telling you the location of that template.

In the example above, the following are potential templates:

html--front.html.twig
html--node.html.twig
html.html.twig

If there was a html--front.html.twig in my sites theme, then that would have been used (assuming I’m on the front page). But because there isn’t a html--front.html.twig or a html—node.html.twig in this theme or the parent theme, so html.html.twig is used.

<!-- BEGIN OUTPUT from 'core/themes/classy/templates/layout/html.html.twig' -->

This is telling you the path to the html.html.twig template that is used. This includes the theme, classy, which in this case is the parent theme.

If I want to customise this, I could simply copy the template from the classy theme and add to my sub theme. After rebuilding the cache, the output would change to:

<!-- BEGIN OUTPUT from 'core/themes/mytheme/templates/layout/html.html.twig' -->

If you want to make changes to use the front page html.html.twig template and leave all other pages as is, then you can create a new file in your theme called html-front.html.twig. As you have seen above, if you are on your sites front page, html-front.html.twig is at the top of the list. This means it will be used if it is available.

After creating the html-front.html.twig template, you’ll have an empty file. The easiest method to create a starting point is to copy the contents of html.html.twig and to html-front.html.twig and then make your changes (alternatively, you could copy the entire file and change the name to html-front.html.twig when pasting in your theme).

Wrapping up

Drupal has a set of rules to determine which template it will use. The process for finding the template and making changes to it is:

Overriding an existing template:

  1. View the page source
  2. Find the template you are looking for
  3. Identify the template being used in the code comments
  4. Copy the template to your theme to override it
  5. Make your changes

Create a more specific template than the one being used:

  1. View the page source
  2. Find the template you are looking for
  3. Identify the specific template from the list in the code comments
  4. Create a new template with that name in your theme
  5. Copy the contents of the generic template to the new template
  6. Make your changes
Jun 06 2018
Jun 06

Adding CSS classes to templates allows you to target templates or parts of the template in your CSS files.

For example, if you want a different background colour per content type, then you need a way to identify each content type with a CSS selector. By default the node template includes a class for each content type, making it easy for you to target each one. You’ll see this in action later on in this tutorial. You will also learn how to create your own classes.

Adding a single class

To add a class, pass in the class name as an argument to the attributes.addClass() function.

<div {{ attributes.addClass('classname') }}>

</div>

Adding multiple classes

To add multiple classes to an element, create an array with all of the class names. To create an array in Twig, use the set tag followed by the name of the array.

{%
  set classes = [
    'content',
    'node',
    'custom',
  ]
 %}

And then pass that array to the attributes.addClass() function.

<div {{ attributes.addClass(classes) }}>

</div>

Classes are merged with existing classes

If there are any existing classes, the new classes will be merged. Therefore, all the classes will be added to the element.

Removing a class

You may decide that you’d like to remove one of the existing classes. To do that, you can use the attributes.removeClass() function.

<div {{ attributes.addClass(classes).removeClass('node') }}>

</div>

Two real world examples

In your code editor, head over to the templates in the Classy theme (core/themes/classy/templates).

Single class

Open up field/time.html.twig. This is an example of a single class being added to the time element.

<time{{ attributes.addClass('datetime') }}>{{ text }}</time>

Multiple class array

Open up layout/region.html.twig. The classes array:

{%
  set classes = [
    'region',
    'region-' ~ region|clean_class,
  ]
%}

In this example, there are two classes. The first one is a simple one called ‘region’. The second one looks more interesting:

'region-' ~ region|clean_class,

Let’s break this down:

  • 'region-': a simple sting
  • ~ is a Twig operator that concatenate two strings together
  • region: the region variable. It will be replaced with the actual region (e.g. block)
  • |: A pipe that separates a variable (region) from a filter (clean_class)
  • clean_class: a filter that will remove invalid characters from HTML classes

If you have a block in the header region, then the above code will add a class called region-header.

<div class="region region-header">

Node class

The final example is the classes added to the article element in the node template.

Open up content/node.html.twig. This is another example of multiple classes.

{%
  set classes = [
    'node',
    'node--type-' ~ node.bundle|clean_class,
    node.isPromoted() ? 'node--promoted',
    node.isSticky() ? 'node--sticky',
    not node.isPublished() ? 'node--unpublished',
    view_mode ? 'node--view-mode-' ~ view_mode|clean_class,
  ]
%}

As mentioned in the example at the top of this tutorial, the node template will have a class for the content type, making it easy for you to specify styles on for specific content types. That is defined with this line:

'node--type-' ~ node.bundle|clean_class,

A content type is a type of bundle in Drupal. So node.bundle will return the content type machine name. Just like the field example, this is passed through the clean_class filter.

If you are viewing an article node, the class added to article will be:

node--type-article

There are also four conditions that check if something exists before adding the class. For example:

node.isPromoted() ? 'node--promoted',

This checks if the node is promoted to the frontage. If it is, it adds the node--promotedclass.

not node.isPublished() ? 'node--unpublished',

This checks if the node is not published. If it isn’t published, it adds the node--unpublished class.

Outro

Hopefully this tutorial has helped you understand how you can add classes to Twig templates. I encourage you learn from more examples by having a look at more templates (in the Classy theme, or another theme).

Nov 24 2017
Nov 24

Drupal 8 represents some major changes to the way we work with themes. There is now Twig, asset libraries, component based CSS, to name a few of the changes. If you are used to working with themes in Drupal 7, all these changes can be overwhelming. But it is well worth the effort - building themes in Drupal 8 is a much improved experience.

Over the course of the coming weeks and months, I’ll be sharing tutorials specifically on Drupal 8 theming to help you get over the learning curve. In this first post, I’m exploring some of the key changes in Drupal 8.

Theme location

You can store your theme in three different locations in your code base in Drupal 8:

  • themes
  • sites/all/themes
  • sites/(site name)/themes

If you are running a multi-site, you’d create your theme in sites/(site name)/themes , with (site name) being the name of the site. Otherwise, the recommended approach is to put your theme in the themes folder.

Compared to Drupal 7

In Drupal 7, the themes folder was reserved for core themes. As such, custom and contributed themes should not be stored there. In Drupal 8, core themes have been moved to the /core folder.

THEMENAME.info.yml

This is where you keep meta-data and settings for the theme. The name of this file should match the name of the theme. This is the only required file in a theme in Drupal 8 and a theme cannot exist without this file.

Here is an example of a simple .info.yml file:

name: Responsive Skeleton
type: theme
description: 'Responsive, mobile friendly base theme based on Skeleton'
package: Core
core: 8.x
libraries:
  - responsive_skeleton/global-styling
regions:
  header: Header
  highlighted: Highlighted
  help:  Help
  content: Content
  sidebar_first: 'Left sidebar'
  sidebar_second: 'Right sidebar'
  footer: Footer

If you’d like to learn more about YAML files, check out my introduction to YAML in Drupal 8.

Compared to Drupal 7

In Drupal 7, this file would be called THEMENAME.info. A lot of the information would be the same but it would be in the INI format rather than YAML. If you have done any Drupal 7 development, the information will look familiar to the above snippet. One of the key differences between Drupal 7 and 8 is that the key value pair are separated with an equals sign (“=“) in the INI format used in Drupal 7, and colon (“:”) in the YAML format used in Drupal 8. There needs to be a space after the colon.

THEMENAME.theme

This is a PHP file used for additional logic (that should not be in template files) and preprocessing variables for templates.

Compared to Drupal 7

In Drupal 7, this file would be called template.php. A lot of the hooks, such as hook_preprocess, are the same (and some have been removed). The change of name from template.php to THEMENAME.theme brings it inline with the naming of module files (MODULENAME.module).

Templates

Templates provide the HTML structure for the site. Drupal 8 uses the Twig theme engine.

Example template files:

  • block.html.twig
  • comment.html.twig
  • node.html.twig
  • page.html.twig

Example twig file (block.html.twig):

{%
  set classes = [
    'block',
    'block-' ~ configuration.provider|clean_class,
    'block-' ~ plugin_id|clean_class,
  ]
%}
<div{{ attributes.addClass(classes) }}>
  {{ title_prefix }}
  {% if label %}
    <h2{{ title_attributes }}>{{ label }}</h2>
  {% endif %}
  {{ title_suffix }}
  {% block content %}
    <div{{ content_attributes.addClass('content') }}>
      {{ content }}
    </div>
  {% endblock %}
</div>

Compared to Drupal 7

The default theme engine in Drupal 7 is PHPTemplate. The templates in Drupal 7 included PHP code. There is no PHP code in the Twig templates in Drupal 8. Twig brings many advantages over PHPTemplate including enhanced security and inheritance.

/css/

This folder contains your theme’s CSS files. In Drupal 8 there is a recommended best practice for how you architect your CSS files that should be followed, which is based on SMACSS and BEM. This helps to create CSS that is reusable (components that can be reused multiple times), maintainable and scalable.

Here is an example for a really simple theme:

css
|- base/
|—- elements.css
|- components/
|—- block.css
|—- breadcrumb.css
|—- buttons.css
|—- header.css
|—- node.css
|—- table.css
|—- views.css
|— colors.css
|- layout.css
|- print.css

The files in the components are small self-contained chunks.

In order for your Drupal site to use the CSS files, they should be listed in THEMENAME.libraries.yml (see below).

THEMENAME.libraries.yml

This file contains a list of CSS and JS libraries to use in your theme.

Here is an example of adding CSS files in a theme’s .libraries.yml file:

libraryname:
  css:
    theme:
      css/style.css: {}
      css/print.css: { media: print }

You can also load CSS files only when needed, rather than for every page in the site. In the following example, node.css is loaded on node pages only.

libraryname:
  css:
    theme:
      css/style.css: {}
      css/print.css: { media: print }

node:
  css:
    theme:
      css/node.css: {}

THEMENAME.breakpoints.yml

Used to define responsive breakpoints for your theme.

Wrapping up

Moving from PHPTemplate to Twig, INI format to YAML, new CSS architecture and managing CSS and Javascript as library assets are some of the main changes in theming in Drupal 8. These changes have improved security and inheritance and made theming more elegant. I’ll be sharing a lot more about each of these in the coming weeks and months! If you’d like to be first to get them, jump on my newsletter list.

Oct 24 2017
Oct 24

Creating a page template for a content type gives you a lot of control over the structure of the page. While Drupal will automatically pick up a node template for a content type, if you use the right naming convention, it will not for a page template. Fortunately it just takes a few lines of code and you can create a page template for any content type you choose.

Before we start, let’s take a brief look at the difference between a node and a page template.

  • Page: the template that controls the whole page. This will include a $content variable, which after rendering will be the node.
  • Node: the template that controls just the content part of the page.

Implement a preprocess hook in template.php

To tell Drupal to use the page template for a content type, you can use template_preprocess_page. This hook is used whenever a page.tpl.php template is used to preprocess variables for it.

In this case, you are going to use template_preprocess_page to tell Drupal to use a specific page template for a specific content type, instead of the default page.tpl.php.

Here is an example of telling Drupal to use a template for a content type. Add this code to the template.php in your theme, and change THEME to the name of your theme. If you have already implemented theme_preprocess_page in your theme’s template.php, you can add the 'if' statement to that.

function THEME_preprocess_page(&$variables) {
  if ($variables['node']->type == 'article') {
    $variables['theme_hook_suggestions'][] = 'page__article';
  }
}

In this example, I’m telling Drupal that I’d like to use the page__article template if the node type is article. This is added to 'theme_hook_suggestions', which adds it to the pool of potential templates that Drupal can use. In most cases it will use this one, unless you have another template that meets a more specific criteria. For example, you might have a page template for a single node, which would be more specific and used instead.

In the example above, you are explicitly mapping a template to a content type. But what if you want the option to add a page template for any content type that you use now and in the future?

Fortunately you can adjust the snippet above to do just that.

function THEME_preprocess_page(&$variables) {
  if (isset($variables['node']->type)) {
    $variables['theme_hook_suggestions'][] = 'page__' . $variables['node']->type;
  }
}

Have a look at this part:

'page__' . $variables['node']->type;

This means that there will be a template suggestion added for all node types. If you have two content types, article and recipes, you can use the following templates:

'page--article';
'page--recipe';

Note that the actual template filename uses hyphens while the theme_hook_suggestions uses underscores.

You can inspect the 'theme_hook_suggestions' array on any path and see which page templates are included.

function TEMPLATE_preprocess_page(&$variables) {
  if (isset($variables['node']->type)) {
    $variables['theme_hook_suggestions'][] = 'page__' . $variables['node']->type;
    dpm($variables);
  }
}

The dpm() will return the following:

Drupal 7 theme hook suggestions

You can see page__article in the array. If you decided you want to create a page template for a specific node, you could do that by creating a template with the name page__node__id and change id to the node ID. This would be used instead of the page__article template. In the example above, this was called on a node with a NID of 420 on my local site. So if I wanted a page template for that node, I’d create a template file called page--node--420.tpl.php

Wrapping up

Using specific page templates for content types is incredibly handy if you want to change the structure of the page. While Drupal doesn’t automatically pick up templates for content types, you can add in template.php with just a few lines of code.

Sep 08 2017
Sep 08

In the previous tutorial, you learnt how to create a modal in a custom module. If you missed it, you can check it out here.

There is just one problem with this: if a user doesn't have Javascript enabled, the modal won't work. While the vast majority of people do have JavaScript enabled, it is still important to gracefully work for those that don't for accessibility reasons (for example, visually impaired people might access the site with a screen reader).

In this case, the best option is to display the contents of the modal on a page, rather than the modal, for those that don't have JavaScript enabled.

Fortunately for us, Drupal handles this out of the box. All you need is a few tweaks to the code that we wrote in the previous tutorial.

The Steps

Start with the module from the previous tutorial -
custom_modal. You can clone from github:

You are going to need to change three files in this modal:

  • custom_modal.routing.yml
  • CustomModalController.php
  • ModalBlock.php

1/ custom_modal.routing.yml

In the routing file, add a slug to the path which will change dynamically if Javascript is enabled or disabled:

path: 'modal-example/modal/{js}'

If Javascript is enabled, Drupal will change this to:

modal-example/modal/ajax

Otherwise, it will be:

modal-example/modal/nojs

This (ajax or nojs) will be passed to the controller, which you will see shortly.

You also need to add js: 'nojs|ajax' to the requirements.

The full code for the route is:

custom_modal.modal:
  path: 'modal-example/modal/{js}'
  defaults:
    _title: 'Modal'
    _controller: '\Drupal\custom_modal\Controller\CustomModalController::modal'
  requirements:
    _permission: 'access content'
    js: 'nojs|ajax'

2/ CustomModalController.php

In the modal method, add $js as an argument. This will be passed from the route and will be either ajax or nojs. This will allow you to change what is returned depending on whether or not Javascript is enabled. If nothing is passed at all, we'll use nojs as the default.

public function modal($js = 'nojs') {

And add the following use statement:

 use Symfony\Component\HttpFoundation\Response;

The full code for CustomModalController.php is now:

<?php

/**
 * @file
 * CustomModalController class.
 */

namespace Drupal\custom_modal\Controller;

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Response;

class CustomModalController extends ControllerBase {

  public function modal($js = 'nojs') {
    if ($js == 'ajax') {
      $options = [
        'dialogClass' => 'popup-dialog-class',
        'width' => '50%',
      ];
      $response = new AjaxResponse();
      $response->addCommand(new OpenModalDialogCommand(t('Modal title'), t('The modal text'), $options));
    }
    else {
      $response = new Response();
      $response->setContent('The modal text (Javascript is not enabled)');
    }

    return $response;
  }
}

3/ ModalBlock.php

Change $link_url to:

$link_url = Url::fromRoute('custom_modal.modal', ['js' => 'nojs']);"

The full code for ModalBlock.php is now:

<?php
/**
 * @file
 * Contains \Drupal\custom_modal\Plugin\Block\ModalBlock.
 */

namespace Drupal\custom_modal\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\Component\Serialization\Json;

/**
 * Provides a 'Modal' Block
 *
 * @Block(
 *   id = "modal_block",
 *   admin_label = @Translation("Modal block"),
 * )
 */
class ModalBlock extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    $link_url = Url::fromRoute('custom_modal.modal', ['js' => 'nojs']);
    $link_url->setOptions([
      'attributes' => [
        'class' => ['use-ajax', 'button', 'button--small'],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => Json::encode(['width' => 400]),
      ]
    ]);

    return array(
      '#type' => 'markup',
      '#markup' => Link::fromTextAndUrl(t('Open modal'), $link_url)->toString(),
      '#attached' => ['library' => ['core/drupal.dialog.ajax']]
    );
  }
}

Testing it without Javascript

You can test this by turning off Javascript in your browser. You can do this in Chrome with the following steps:

  • click on Settings
  • Advanced (right at the bottom of the settings page)
    • Content settings
    • Javascript
    • Toggle to blocked
    • Refresh the page and hit the "Open Modal" button in the block

If you click on the link for the modal, it should now display the non-JS version on the custom path.

Wrapping up

To see the complete code, have a look at the branch on GitHub. You can also check out the difference between this and the previous version here.

Aug 16 2017
Aug 16

Last week we looked at creating a link that opens in a modal by adding a few attributes to the link. We are going to take this one step further this week by creating the modal in a custom module. This will give you much more flexibility over what you include in the modal.

Create the module info file

In this example, I’m going to call the module custom_modal.

First, create the module info.yml file in the module folder (called custom_modal). The custom_modal.info.yml file provides basic information to Drupal about the module. Add the following to custom_modal.info.yml:

name: Custom Modal
type: module
description: Display a modal
core: 8.x
package: Custom

Create the path for the modal

Last week, you added a link to a block and clicking on the link triggered the modal. Here is a recap of that link:

<p><a class=“use-ajax” data-dialog-type=“modal" href="https://befused.com/search/node">Search</a></p>

With a custom module, you will need to create the link in code and clicking on that link will trigger the modal. To do that in Drupal 8, you can register a route.

Create the route file, called custom_modal.routing.yml. In custom_modal.routing.yml, add the following route:

custom_modal.modal:
  path: 'modal-example/modal'
  defaults:
    _title: 'Modal'
    _controller: '\Drupal\custom_modal\Controller\CustomModalController::modal'
  requirements:
    _permission: 'access content'

When the user clicks on modal-example/modal, the modal will be triggered. The controller is '\Drupal\csc_modal\Controller\CustomModalController::modal'. That means that when this path is called, this controller will be called.

Create the controller

Next you are going to create the controller mentioned above. In the root of the custom_modal folder create:

  • a folder called src
  • a folder inside src called Controller
  • a file inside Controller called CustomModalController.php

And add the following code to CustomModalController.php:

<?php

/**
 * @file
 * CustomModalController class.
 */

namespace Drupal\custom_modal\Controller;

use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Controller\ControllerBase;

class CustomModalController extends ControllerBase {

  public function modal() {
    $options = [
      'dialogClass' => 'popup-dialog-class',
      'width' => '50%',
    ];
    $response = new AjaxResponse();
    $response->addCommand(new OpenModalDialogCommand(t('Modal title'), t('The modal text'), $options));

    return $response;
  }
}

This creates an Ajax command to open a dialog modal. When the modal is open, it will contain the text “This modal text”.

You could use this to show anything else you want in a modal. For example, you might decide to define a form so that users can complete an action and show that in the modal.

Create the block

And finally, you can create the block for this, which will contain the button to call the modal.

In the root of the custom_modal folder create:

  • a folder inside src called Plugin
  • a folder inside Plugin called Block
  • a file inside Block called ModalBlock.php

And add the following code to ModalBlock.php:

<?php
/**
 * @file
 * Contains \Drupal\custom_modal\Plugin\Block\ModalBlock.
 */

namespace Drupal\custom_modal\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Url;
use Drupal\Core\Link;
use Drupal\Component\Serialization\Json;

/**
 * Provides a 'Modal' Block
 *
 * @Block(
 *   id = "modal_block",
 *   admin_label = @Translation("Modal block"),
 * )
 */
class ModalBlock extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    $link_url = Url::fromRoute('custom_modal.modal');
    $link_url->setOptions([
      'attributes' => [
        'class' => ['use-ajax', 'button', 'button--small'],
        'data-dialog-type' => 'modal',
        'data-dialog-options' => Json::encode(['width' => 400]),
      ]
    ]);

    return array(
      '#type' => 'markup',
      '#markup' => Link::fromTextAndUrl(t('Open modal'), $link_url)->toString(),
      '#attached' => ['library' => ['core/drupal.dialog.ajax']]
    );
  }
}

This is creating a link for the block using the URL directly from the route that was defined earlier in custom_modal.routing.yml:

$link_url = Url::fromRoute('custom_modal.modal');

It then sets the attributes for that link. You’ll notice that this code sets a CSS class of use-ajax and a data-dialog-options attribute with a value of modal:

 'attributes' => [
        'class' => ['use-ajax', 'button', 'button--small'],
        'data-dialog-type' => 'modal',

This is the same as the link you were shown last week:

<p><a class="use-ajax" data-dialog-type="modal" href="https://befused.com/search/node">Search</a></p>

The code then creates the link from using the $link_url:

 '#markup' => Link::fromTextAndUrl(t('Open modal'), $link_url)->toString(),

Open modal will be the anchor text for the link, which you can change to what ever you want.

And finally it attaches Drupal’s dialog library, which uses jQuery UI dialog:

'#attached' => ['library' => ['core/drupal.dialog.ajax']]

Add the block to a region

Head on over to the Extend menu in your Drupal site and enable this module (or use Drush).

Now you need to enable the block and add it to a region. Head over to Block Layout and add it to a region of your choice. In this example, I’m going to add it to sidebar first.

After clicking on the Place Block button next to the region, you can search for the modal block you just created by its name (“modal block”).

Head back to the main site and you should see the block in the side bar.

Open modal in Drupal 8

And this will open the modal!

Even though this example doesn’t do anything other than display text in a modal, it gives you the tools to create your own modal in code and do a lot more in the modal - such as add a custom form. We’ll be looking at doing just that in the near future (stay tuned). We'll also be looking into how to gracefully deal with users who don't have Javascript enabled.

Aug 07 2017
Aug 07

Modal dialogs are incredibly useful on websites as they allow the user to do something without having to leave the web page they are on. Drupal 8 now has a Dialog API in core, which greatly reduces the amount of code you need to write to create a modal dialog. Dialogs in Drupal 8 leverage jQuery UI.

In this first part in the series on the modal API in Drupal 8, we are going to create a search link in a block in the sidebar. When a user clicks on the search link, they will be presented with the modal with Drupal’s search form.

The first step is to add a block to the side bar. Head over to Block Layout and add it to a region of your choice. In this example, I’m going to add it to sidebar first.

Place a block button in Drupal 8

Select Add custom block.

Button to add a custom block in Drupal 8

In the block settings, change the text format to Full HTML and click on the Source button to remove the rich text editor. This will allow you to add a small HTML snippet without the rich text editor trying to encode the HTML.

In the body of the block, add the following snippet:

<p><a class="use-ajax" data-dialog-type="modal" href="https://befused.com/search/node">Search</a></p>

And now if you head back to the site, you will see the block in the side bar. Click on the search link, and you will get the modal.

Search modal

Adjusting the width

You can adjust the width by adding the data-dialog-options attribute:

data-dialog-options="{&quot;width&quot;:800}"

I’m including the HTML entity &quot; because that will end up being a quote mark in the rendered HTML.

The full snippet with a width of 800 pixels is:

<p><a class="use-ajax" data-dialog-options="{&quot;width&quot;:800}" data-dialog-type="modal" href="https://befused.com/search/node">Search</a></p>

Wrapping up

You can use this method to display any valid Drupal path in a modal. For example, if you want to show a node in a modal, change the href to the path for the node.

<p><a class="use-ajax" data-dialog-options="{&quot;width&quot;:800}" data-dialog-type="modal" href="https://befused.com/node/2">See node 2</a></p>

These examples show the power of Drupal 8’s dialog API. With just a couple of attributes in a standard link, you can display any path in a modal.

This is a very simple example to help get you started. In most cases, adding modals is done in custom modules or themes. To find out how to do this in a custom module, check out the next tutorial in the series: Create a modal in Drupal 8 in a custom module

Jun 15 2017
Jun 15

When you are calling a function or method that has an argument(s), you don’t always know what type (array, object, string etc) of argument to pass to the function. And if you end up passing in the wrong type, you’ll get a less than helpful error.

To fix this, you can using type-hints to improve the clarity of your code and improve errors. Type hinting allows you to specify data types you expect for an argument for a function declaration. You can specify data types such as arrays, objects and interfaces. Type-hinting is optional, but it does makes it easier to debug your code and allows your IDE to provide autocompletion amongst other benefits.

Consider the following simple example:

<?php

$ingredients  = array('banana', 'strawberry');

function shake ($ingredients) {
  foreach($ingredients as $ingredient) {
    print $ingredient;
  }
}

shake($ingredients);


This will print banana and strawberry to the screen.

But it isn’t immediately clear that the $ingredients argument in the shake function needs to be an array. So what happens if you pass in a string instead?

$ingredients = 'coconut';

function shake ($ingredients) {
  foreach($ingredients as $ingredient) {
    print $ingredient;
  }
}

shake($ingredients);


You will get the following error:

Warning: Invalid argument supplied for foreach()

The error is referencing the foreach because you have tried to pass it a string when it was expecting an array.

To use a type-hint with this example, add the date type to the function argument. In this case, the data type is array.

function shake (array $ingredients) {
  foreach($ingredients as $ingredient) {
    print $ingredient;
  }
}

Then if you pass in the string, you will get a more meaningful error:

$ingredients = 'coconut';

function shake (array $ingredients) {
  foreach($ingredients as $ingredient) {
    print $ingredient;
  }
}

shake($ingredients);

The error is now:

Catchable fatal error: Argument 1 passed to shake() must be of the type array, string given, called in /Users/blairwadman/Sites/tutorials/typehinting/index.php on line 13 and defined in /Users/blairwadman/Sites/tutorials/typehinting/index.php on line 7

This is very meaningful and tells you exactly what the problem is and the line it is on.

Object type hinting

You can also specify a class or interface as the type-hint.

Consider the following Drupal implementation of hook_form_alter():

<?php
/**
 * Implements hook_user_login($account).
 *
 * @param $account
 */
function welcome_user_login($account) {
  $name = $account->getDisplayName();
  drupal_set_message(t('Hello @name, thank you for logging in and welcome!', ['@name' => $name]));
}

You now have the display name from $account. But you have to know that getDisplayName() is an available method.

Fortunately, there is an easier way if you use a type-hint and an IDE like PHPStorm.

If you include a base class or interface for the $account variable, your IDE will tell you the available methods and properties.

Add ‘AccountInterface’ type-hint to the welcome_user_login() function:

function welcome_user_login(\Drupal\Core\Session\AccountInterface $account) {

And now when you start to type $account-> , the IDE will offer you autocompletion.

Adding a use statement

You can also include a use statement in the .module file so that you can reference 'AccountInterface' rather than the full namespace hierarchy.

At the top of .module file, add the following use statement:

use Drupal\user\AccountInterface;


Change the function argument:

function welcome_user_login(AccountInterface $account) {


Putting this all together, welcome_user_login() is now:

<?php

use Drupal\user\UserInterface;

/**
 * Implements hook_user_login($account).
 *
 * @param $account
 */
function welcome_user_login(UserInterface $account) {
  $name = $account->getDisplayName();
  drupal_set_message(t('Hello @name, thank you for logging in and welcome!', ['@name' => $name]));
}


Wrapping up

Using type-hints in your code makes it clearer and less error prone, improves the errors you get back and also aids in autocompletion. Type-hints aren’t part of the Drupal code standards yet (so they are not required for core and contributed code) yet, but you find plenty of examples in core code.

May 18 2017
May 18

Acquia has announced that they will be ending the life of Mollom. As of April 2018, they will no longer support the product.

You have over a year to find a replacement. I am currently using Mollom and planning on changing mine now. Chances are, if I don't, I'll forget to change it closer to the time!

I wrote about various Drupal spam protection methods in this post a couple of years ago. There were many options, as there are now. At the time, I went with the Mollom and Honeypot combination, which I am still using today.

Acquia has recommended a combination of reCAPTCHA and Honeypot. That is what I am planning on switching to.

Full list of other options

While there are a lot of options available to you, I've summarised the main ones here. To see the full list, you can check out the Spam Prevention project category on Drupal.org.

reCAPTCHA

The reCAPTCHA module uses Google's reCAPTCHA service. reCAPTCHA will show suspicious users an "I'm not a robot" checkbox and let through most human users.

To use reCAPTCHA you also need to install the CAPTCHA module.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: Stable

reCAPTCHA project page.

Honeypot

Honeypot tricks spam bots by adding a field that only they fill in. The field is named something like "homepage", so they think it is a real field. In fact, only spam bots see the field and it is hidden from real people. If the field is filled in, the comment gets blocked. Honeypot also uses time detection. So if a comment is created in less than 5 seconds, it is more likely to be a spam bot than a human, so it gets blocked.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: Stable

Honeypot project page.

Hashcash

According to its website, Hashcash is "a proof-of-work algorithm, which has been used as a denial-of-service counter measure technique in a number of systems."

Sounds funky. Friends of mine have said that it worked for them.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: Dev version

Hashcash project page: Stable Drupal

Captcha questions

Image based captchas are generally annoying for users and not totally effective. Spam bots have found ways to complete them. Mollom actually uses an image based captcha if it detects a possible bot.

But there is an alternative which people have had success with: question based captcha. Rather than showing the user a difficult to read image, present them with a simple question, such as "what is two plus ten" or "what is the capital of France". This seems to be more effective against bots, and also a nicer experience for humans. Sure, you have to apply some thought to come up with the answer. But isn't that easier than trying to figure out the characters in image based captchas?

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: Dev version

Captcha Questions project page

CAPTCHA

The CAPTCHA module tries to prevent spambots by asking a questions that only humans can complete. reCAPTCHA mentioned above extends CAPTCHA.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: Beta version

Captcha project page

Antispam

The Antispam module supports 3rd party services Akismet, TypePad AntiSpam and Defensio.

According to the project page, there are known bugs in the stable version and the dev release is recommended for now.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: None

Antispam project page

BlogSpam

The BlogSpam module supports the 3rd party BlogSpam service.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: None

BlogSpam

Spamicide

The Spamicide module helps to prevent spam by adding a form field that only spam bots see and fill in.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: Dev version

Spamicide project page

BOTCHA

BOTCHA works in a similar way to Spamicide in that it adds a field that bots fill in but humans don't see.

Some people have suggested that it is more effective than Spamicide. If this is something you want to try, I recommend you test both and see which one is more effective.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: None available

BOTCHA

Bad Behaviour

This module integrates Drupal with the 3rd party Bad Behaviour system.

Bad Behaviour is different to the other modules mentioned in that it prevents spam bots from even getting a request from Drupal. This reduces system resources. Check out the about page for more information.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: None available

Bad Behaviour

http:BL

This module integrates with the 3rd party http:BL system. It will block spam bots from accessing your site by checking against Project Honey Pot data. You need a free Project Honey Pot account to take advantage of this service.

Drupal Versions

  • Drupal 7: Stable
  • Drupal 8: Dev version

http:BL

Wrapping up

There are a lot of options when it comes to preventing spam on your Drupal site, so there is no need to worry about the loss of Mollom. None of the options available will prevent all spambots, so you can test a few and see which ones are the most effective for you.

May 12 2017
May 12

Creating an admin form is often one of the first things you'll need to do in a custom Drupal module. An admin interface enables you to make a module's settings configurable by a site editor or administrator so they can change them on the fly.

If you've created admin forms in Drupal 7 previously, you'll see some pretty big differences in the way you define forms between Drupal 7 and Drupal 8, and there are still a lot of similarities. Forms are represented as nested arrays in both Drupal 7 and Drupal 8. But they are different in that in Drupal 7 you define your form arrays in functions and in Drupal 8 you create a form class.

In this step by step tutorial, you are going to learn how to create an admin form in Drupal 8 with a route and menu item and then use the saved form data.

It is worth pointing out that you could create most of this code with the Drupal Console. However, the best way to truly learn it is to write the code without using tools like Console to create it for you. After that, it is a create idea to use Drupal Console to create code like this for you. To learn more, you can check out my 7 day course.

Step 1: Module setup

Create the module folder

  • Create a new directory in /sites called all
  • Inside all create a directory called modules
  • Inside modules create a directory called custom
  • Create the directory called welcome inside the custom directory

Create the YAML info file

The info YAML file tells Drupal that your module exists and provides important information, such as its human readable name, machine name, description and version number.

The filename should be the machine name of your module with the .info.yml extension. In this case, it will be welcome.info.yml.

Create welcome.info.yml in the root of the welcome directory.

Add the following to welcome.info.yml:

name: Welcome  
type: module  
description: Display a message when a user logs in  
core: 8.x  
package: Custom  

The info.yml file is all you need to register the module with the Drupal system. If you head on over to Extend, you will see the Welcome module listed.

You can go ahead and enable the module (click install at the bottom of the Extend page or use Drush).

Setup the rest of the files and folders

Setup the following files and folders in the welcome module folder:

  • Create a file called welcome.routing.yml.
  • Create a file called welcome.links.menu.yml.
  • Create a file called welcome.module
  • Create a folder called src.
  • In the src folder, create a new folder called Form.
  • In the Form folder, create a file called MessagesForm.php.

Module file structure

Your module file structure should now look like this:

Drupal 8 admin form structure

Step 2: Create form

There are a few parts to creating an admin form:

  • The form itself
  • A route mapping the form to a URL, so that you can access it
  • A menu item in Drupal’s menu system, so that it can be accessed from the admin menu

Set out basic class

In MessagesForm.php, set out the basic class:

<?php  
/**  
 * @file  
 * Contains Drupal\welcome\Form\MessagesForm.  
 */  
namespace Drupal\welcome\Form;  
use Drupal\Core\Form\ConfigFormBase;  
use Drupal\Core\Form\FormStateInterface;  

class MessagesForm extends ConfigFormBase {  

}  

ConfigFormBase is a base class that is used to implement system configuration forms.

Methods to use

You are going to add the following methods to this class:

  • getEditableConfigNames() - gets the configuration name
  • getFormId() - returns the form’s unique ID
  • buildForm() - returns the form array
  • validateForm() - validates the form
  • submitForm() - processes the form submission

Add getEditableConfigNames() and getFormId()

Start off by adding getEditableConfigNames() and getFormId():

<?php  

/**  
 * @file  
 * Contains Drupal\welcome\Form\MessagesForm.  
 */  

namespace Drupal\welcome\Form;  

use Drupal\Core\Form\ConfigFormBase;  
use Drupal\Core\Form\FormStateInterface;  

class MessagesForm extends ConfigFormBase {  
  /**  
   * {@inheritdoc}  
   */  
  protected function getEditableConfigNames() {  
    return [  
      'welcome.adminsettings',  
    ];  
  }  

  /**  
   * {@inheritdoc}  
   */  
  public function getFormId() {  
    return 'welcome_form';  
  }  
}  


The buildForm() method

Next you can add the buildForm method to the MessagesForm class.

  /**  
   * {@inheritdoc}  
   */  
  public function buildForm(array $form, FormStateInterface $form_state) {  
    $config = $this->config('welcome.adminsettings');  

    $form['welcome_message'] = [  
      '#type' => 'textarea',  
      '#title' => $this->t('Welcome message'),  
      '#description' => $this->t('Welcome message display to users when they login'),  
      '#default_value' => $config->get('welcome_message'),  
    ];  

    return parent::buildForm($form, $form_state);  
  }  

Breakdown of buildForm()

Let’s break this down and see what each part is doing. 

$config = $this->config('welcome.adminsettings');  

This initialises the config variable. welcome.adminsettings is the module's configuration name, so this will load the admin settings.  

There is just one element to this form, the welcome message textarea:

      $form['welcome_message'] = [  
        '#type' => 'textarea',  
        '#title' => $this->t('Welcome message'),  
        '#description' => $this->t('Welcome message display to users when they login'),  
        '#default_value' => $config->get('welcome_message'),  
      ];  

The default value is returned from the configuration object. It calls the get() method with the name of the property to get, which is welcome_message. You will add the code to save this when the form saves shortly.

The title and description are displayed when you view the form.

Step 2: Save form data

The submitForm() method

Next you can add the submitForm method.

  /**  
   * {@inheritdoc}  
   */  
  public function submitForm(array &$form, FormStateInterface $form_state) {  
    parent::submitForm($form, $form_state);  

    $this->config('welcome.adminsettings')  
      ->set('welcome_message', $form_state->getValue('welcome_message'))  
      ->save();  
  }  


The submitForm() method is responsible for saving the form data when the form is submitted. Take a look at the code inside the method:

    $this->config('welcome.adminsettings')  
      ->set('welcome_message', $form_state->getValue('welcome_message'))  
      ->save();  

Let’s break this down line by line:

  • $this is the admin settings form class.
  • -> is an object operator
  • config('welcome.adminsettings’): welcome.adminsettings is the name of the module's configuration.
  • ->set('welcome_message', $form_state->getValue('welcome_message’)): here it is setting ‘welcome_message’ and get the values from the form state.
  • ->save: save the form data.

Setting and Getting Configuration Objects

In the previous section, you have set the value for a configuration object in the submit function. And then in the method which defines the form, you have retrieved the configuration and configuration setting.

In submitForm(), retrieve the configuration:

$this->config('welcome.adminsettings')  

Also in submitForm(), set the configuration setting:

 ->set('welcome_message', $form_state->getValue('welcome_message'))  

In buildForm(), retrive the configuration:

$config = $this->config('welcome.adminsettings');  

Also in buildForm(), get the configuration setting:

$config->get('welcome_message'),  

Drupal 8 form configuration

Step 3: Access the form

The route

In order to access the form you just created, you need to add a route for it. This will map the URL to the form.

Open up welcome.routing.yml and add the following route:

welcome.admin_settings_form:  
  path: '/admin/config/welcome/adminsettings'  
  defaults:  
    _form: '\Drupal\welcome\Form\MessagesForm'  
    _title: 'MessagesForm'  
  requirements:  
    _permission: 'access administration pages'  
  options:  
    _admin_route: TRUE  

The route is mapping to a form controller (\Drupal\welcome\Form\MessagesForm). When you go to /admin/config/welcome/adminsettings you will get that form. 

Messages Form

If you add a message and submit the form and then refresh the page, the message will remain in the textarea as it is now saved to the database and used as the default message for the form field.

Clear the cache and then go to /admin/config/welcome/adminsettings to get the form.

Menu item

With the route mapped to the form controller, you can hit the URL to get the form.  To make this more useful, we need to add it to Drupal’s menu system. 

As mentioned previously, Drupal 8 defines menu items in a .yml file. Add the following to welcome.links.menu.yml:

welcome.admin_settings_form:  
  title: 'Welcome message configuration'  
  route_name: welcome.admin_settings_form  
  description: 'Welcome message admin'  
  parent: system.admin_config_system  
  weight: 99  

Let’s break this down:

  • Title: This will appear as the menu link. 
  • Route: This is the route that you defined earlier. This maps to the form class.
  • Description: This will appear under the title
  • Parent: This menu item will be nested under the parent menu item
  • Weight: This determines the relative positioning of the menu item in relation to its siblings

After you clear the cache, you should now see this in menu item under admin/config.

Step 4: use the form data

You could use the saved form data in a variety of places. To demonstrate its use, you are how to show the saved message to users when they login to the site.

To do that, you need to add an implementation of hook_user_login() to retrieve the config value.

In welcome.module, create welcome_user_login():

<?php

/**
 * @file
 * Contains welcome.module.
 */

function welcome_user_login() {

}

You can get the configuration for the module using the Drupalconfig() static method with the name of the module’s configuration (welcome.adminsettings).

$config = \Drupal::config('welcome.adminsettings');  

And then get the value for the welcome_message property with the get() method:

$config->get('welcome_message')  

Putting it all together in the hook implementation:

function welcome_user_login($account) {  
  $config = \Drupal::config('welcome.adminsettings');  
  drupal_set_message($config->get('welcome_message'));  
}  

Next, clear the cache for the changes to take effect.

And now, when you log out of the site and log back in again, you should see the message.

Wrapping up

You have created a simple admin form, saved the value to the database and then used that saved value and displayed it to users when they login.

Mar 15 2017
Mar 15

This is part of the series to help you transition to Drupal 8 module development by comparing it with Drupal 7. This week, we are going to look at admin forms.

As an example, we will look at a very simple admin form that has one field, a textarea. The form will allow site editors and administrators to change a message. That message can be used elsewhere in the system, such as displaying it to users when they log in.

In Drupal 7, you create a form using the Form API and define it as an array. In Drupal 8, you need to define a class and like Drupal 7, you define the form as an array.

Creating an admin form in Drupal 7

This form definition could live in the .module file or in a file called welcome.admin.inc (replace "welcome" with the name of your module) in your custom module.

<?php
/**
 * Implements hook_form().
 * Admin form to configurable welcome message 
 */
function welcome_admin_form($form, &$form_state) {
  $form['welcome_message'] = array(
    '#type' => 'textarea',
    '#title' => t('Welcome message'), 
    '#description' => t('Welcome message display to users when they login'),
    '#default_value' => variable_get('welcome_message', 'welcome'),

  );

  return system_settings_form($form);
}

Forms in Drupal 7 are defined as an array, which Drupal will then use to render as a HTML form.

Using system_settings_form()

In the return statement, $form is passed through another function, system_settings_form(). This handy function takes care of common tasks that are needed for any admin form, such as submit buttons and saving form data to the database. It will save it to the variables table. If you are defining a form that is not an admin form, then you would add the submit buttons and handle saving form data yourself.

Using variable_get()

The default value uses variable_get() to retrieve the data from the variables table.

variable_get() is a built-in Drupal function that will get any variable. The first argument is the variable name. The second is a default value, which will be used if the variable does not yet exist in the database. The default value is required, so you need to add something. I have used the word "welcome".

Learn more about admin forms in Drupal 7.

Creating an admin form in Drupal 8

The form definition in Drupal 8 will seem more complicated at first, but there are a lot of similarities with how it is done in Drupal 7.

A form with the same fields as the Drupal 7 example would look like this:

<?php

/**
 * @file
 * Contains Drupal\welcome\Form\MessagesForm.
 */

namespace Drupal\welcome\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

class MessagesForm extends ConfigFormBase {
  /**
   * {@inheritdoc}
   */
  protected function getEditableConfigNames() {
    return array(
      'welcome.adminsettings',
    );
  }

  /**
   * {@inheritdoc}
   */
  public function getFormId() {
    return 'welcome_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $config = $this->config('welcome.adminsettings');

    $form['welcome_message'] = array(
      '#type' => 'textarea',
      '#title' => $this->t('Welcome message'),
      '#description' => $this->t('Welcome message display to users when they login'),
      '#default_value' => $config->get('welcome_message'),
    );
    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    parent::submitForm($form, $form_state);

    $this->config('welcome.adminsettings')
      ->set('welcome_message', $form_state->getValue('welcome_message'))
      ->save();
  }
}

This example is in a file called MessagesForm.php which is in a folder called Form in a custom module.

When creating a simple configuration form, you should extend DrupalCoreFormConfigFormBase. ConfigFormBase is a base class that is used to implement system configuration forms. In other cases, you can extend the more generic FormBase class.

The following methods are used:

  • getEditableConfigNames() - gets the configuration name
  • getFormId() - returns the form’s unique ID
  • buildForm() - returns the form array
  • validateForm() - validates the form
  • submitForm() - processes the form submission

Form field array

The form is defined as an array, just like it is in Drupal 7.

Drupal 8 form field array:

$config = $this->config('welcome.adminsettings');
$form['welcome_message'] = array(
  '#type' => 'textarea',
  '#title' => $this->t('Welcome message'),
  '#description' => $this->t('Welcome message display to users when they login'),
  '#default_value' => $config->get('welcome_message'),
);

Drupal 7 form field array:

$form['welcome_message'] = array(
    '#type' => 'textarea',
    '#title' => t('Welcome message'), 
    '#description' => t('Welcome message display to users when they login'),
    '#default_value' => variable_get('welcome_message', 'welcome'),
  );

As you can see, the forms are almost identical. The main difference is the default value. Where Drupal 7 uses variable_get() to get the saved variable value from the database, Drupal 8 uses $config->get. Drupal 8 uses configuration objects instead of variables for simple admin forms like this. This code calls the get() method with the name of a configuration object property to get, which is welcome_message.

$config = $this->config('welcome.adminsettings');

This initialises the config variable. ‘welcome.adminsettings' is the module's configuration name, so this will load the admin settings.  

'#default_value' => $config->get('welcome_message'),

The default value is returned from the configuration object. It calls the get() method with the name of the property to get, which is welcome_message.

Saving the data

As mentioned above, Drupal 8 uses configuration objects to save the data.

The submitForm() method is responsible for saving the form data when the form is submitted.

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    parent::submitForm($form, $form_state);

    $this->config('welcome.adminsettings')
      ->set('welcome_message', $form_state->getValue('welcome_message'))
      ->save();
  }

The configuration item is saved in the submitForm() method. Let’s break this down line by line:

  • $this is the admin settings form class.
  • -> is an object operator
  • config('welcome.adminsettings’): 'welcome.adminsettings' is the name of the module's configuration.
  • ->set('welcome_message', $form_state->getValue('welcome_message’)): here it is setting ‘welcome_message’ and gets the values from the form state.
  • ->save: save the form data.

In Drupal 7, you don't need a submit function for a simple admin form because system_settings_form() will take care of submitting and saving the form data.

Next steps

In the next few weeks, I will share a complete walk through of creating an admin form in Drupal 8, including the route, menu item and a look at how the data is saved in the database. If you don't want to miss out on that, jump on my newsletter

Mar 01 2017
Mar 01

Routes and menu items are a critical part of the Drupal system. Without them, users would not be able to access the content of a website or any other page. Conceptually Drupal 7 and 8 are the same: map a URL to a function that is responsible for getting and returning the content. But the way it is implemented is completely different (as is the case with most things under the hood between Drupal 7 and 8).

If you are totally new to Drupal 8, you might want to checkout my tutorial on Understanding Drupal 8 routes and controllers as well.

Basic Menu and Routes in Drupal 7

In Drupal 7, you add a route and menu item in the same place by implementing hook_menu. You can then map the menu item to a callback function.

/**
 * Implements hook_menu(). 
 */
function first_module_menu() {
  $items['first/custom'] = array(
    'title' => 'Custom page',
    'page callback' => 'first_module_custom', 
    'access arguments' => array('access content'),
  );

  return $items;
}

In the above snippet, first/custom is the path, which is mapped to the callback function called first_module_custom. In other words, if your Drupal site's domain was example.com, if you go to example.com/first/custom, you could get the content returned from the callback function first_module_custom.

A very simple callback function would look like this:

function first_module_custom() {
  $content = t('Hello world');

  return $content;
}

Basic Menu and Routes in Drupal 8

To do the same thing in Drupal 8, you would need to create a route, utilising the new routing system in Drupal 8 and separately register the menu item.

Routes in Drupal 8

In Drupal 8, routes are based on Symfony’s routing system. Routes are defined in their own YAML file in the root of the module folder and take the format modulename.routing.yml

first_module.content:
  path: '/first/custom'
  defaults:
    _controller: 'Drupal\first_module\Controller\FirstController::content'
    _title: 'Custom page'
  requirements:
    _permission: 'access content'

This maps the URL path for /first/custom to a controller, which now acts as the page callback.

A simple controller to do the same thing as the call back in the Drupal 7 example would look like this:

class FirstController extends ControllerBase {

  public function content($name) {
    return array(
      '#type' => 'markup',
      '#markup' => $this->t('Hello world'),
    );
  }
}

Menu links

To register the path as a menu link in Drupal 8, you need to create a YAML file for your module, using the modulename.menu.yml naming convention (replace modulename with the actual module name). Here is an example:

first_module.demo:
  title: 'Custom page'
  route_name: first_module.content
  description: 'Hello world message on custom page'
  parent: main
  menu_name : main

Arguments

You can pass arguments from the URL to the callback function, which can then be used to affect the content returned. In the examples above, we registered the path of 'first/custom'. You could allow users to go to 'first/custom/123', where 123 is passed to the callback.

Arguments in Drupal 7

In Drupal 7, you would do this by adding a wildcard (%) to the end of the registered path and adding a page arguments property.

/**
 * Implements hook_menu(). 
 */
function first_module_menu() {
  $items['first/custom/%'] = array(
    'title' => 'Custom page',
    'page callback' => 'first_module_custom', 
    'page arguments' => array(1),         
    'access arguments' => array('access content'),
  );

  return $items;
}

The callback function can then take that argument and use it to affect the content returned.

function first_module_custom($name) {
  // View of node.
  $content = t('Hello @name', array('@name' => $name));

  return $content;
}

You can call the parameter in first_module_custom anything, it doesn't have to be $name.

Arguments in Drupal 8

In Drupal 8, you can add a similar wildcard, this time with a named placeholder (otherwise known as a slug). In the route below the placeholder '{name}' has been added to the path.

first_module.content:
  path: '/first/{name}'
  defaults:
    _controller: 'Drupal\first_module\Controller\FirstController::content'
    _title: 'Hello world'
    name: 'Bob'
  requirements:
    _permission: 'access content'

This is passed to the controller as an argument. In the example below, the argument is a variable called $name in the welcome method(). It is then used in $this->t().

<?php
/**
 * @file
 * Contains \Drupal\first_module\Controller\FirstController.
 */

namespace Drupal\first_module\Controller;
use Drupal\Core\Controller\ControllerBase;

class FirstController extends ControllerBase {

  public function content($name) {
    return array(
      '#type' => 'markup',
      '#markup' => $this->t('Hello %name', array('%name' => $name)),
    );
  }
}

?>

For more information on using arguments in Drupal 8 routes, check out - making a Drupal 8 route dynamic.

Permissions

Permissions allow you to restrict who can access menu items. Specific user roles have access to particular permissions.

Permissions in Drupal 7

Use 'access arguments' in Drupal 7 to restrict access to a path.

In the following example, access to 'first/custom' is given to anyone who has been granted the 'access content' permission, which on most Drupal sites will be everyone.

/**
 * Implements hook_menu(). 
 */
function first_module_menu() {
  $items['first/custom'] = array(
    'title' => 'Custom page',
    'page callback' => 'first_module_custom', 
    'page arguments' => array(1),  
    'access arguments' => array('access content'),
  );

  return $items;
}

Permissions in Drupal 8

In Drupal 8, the permissions are granted in the route YAML file using the requirements and _permission keys.

first_module.content:
  path: '/first/{name}'
  defaults:
    _controller: 'Drupal\first_module\Controller\FirstController::content'
    _title: 'Hello world'
    name: 'Bob'
  requirements:
    _permission: 'access content'

Defining your own permission

In the above examples, 'access content' is a permission that is already defined in every Drupal site. You can create your own permission to give you more control.

After you have defined the permission, you can grant access to it for particular roles in the admin section (Admin > People > Permissions).

Defining your own permission in Drupal 7

You can define your own permission in Drupal 7 by implementing hook_permission() in the module's .module file. Here is an example:

function first_module_permission() {
    return array(
        'access first' => array(
            'title' => t('Access to first module content'),
            'description' => t('Allow users access to first module content'),
        )
    );

}


Once this is defined, if you head to the permission page in the admin section, you will see access first listed and you will be able to grant access to it to particular roles.

In your module's implementation of hook_menu, change access arguments to reference the access first permission defined above.

function first_module_menu() {
  $items['first/custom'] = array(
    'title' => 'Custom page',
    'page callback' => 'first_module_custom', 
    'page arguments' => array(1),     
    'access arguments' => array('access first'),
  );

  return $items;
}

Defining your own permission in Drupal 8

To define your own permission in Drupal 8, create a new file called: modulename.permissions.yml.

And in that file, define your permission like this:

access first:
  title: 'Access to first module content'
  description: 'Allow users access to first module content'
  restrict access: TRUE

To use this permission for a particular path, change the _requirements in the route .yml file to reference the access first permission:

first_module.content:
  path: '/first/{name}'
  defaults:
    _controller: 'Drupal\first_module\Controller\FirstController::content'
    _title: 'Hello world'
    name: 'Bob'
  requirements:
    _permission: 'access first'

Outro

Even though the way you define routes, menu items and permissions in Drupal 8 is totally different to Drupal 7, the overall concept is the same. Define a path, map it to a callback/controller and restrict access to a particular permission.

Jan 26 2017
Jan 26

If you are used to creating Drupal 7 modules, one of the major challenges of moving to Drupal 8 is all the changes under the hood. Almost everything has changed and that can be very overwhelming. But it doesn't have to be. One method to learn how to create Drupal 8 modules is to compare it to what you are used to in Drupal 7 module development.

In the first part of this series, you are going to learn how to create a block programmatically in Drupal 8 by comparing it to Drupal 7.

Drupal 7 - defining and viewing a block

In Drupal 7, blocks are defined using the hook system. You use hook_block_info() to register the block and hook_block_view() when generating content for the block.

Drupal 8 - defining and viewing a block

In Drupal 8, blocks are defined using the new Plugin architecture, which provides swappable pieces of functionality. To programmatically create a block in Drupal 8, you need to define a Block plugin class.

Getting started in Drupal 7

All of the block code can live in the module's .module file.

Setup a module

To setup the skeleton of the module:

  1. Go to your sites sites/all/modules directory
  2. Create a directory for the module called hello
  3. Create a file called hello.info and add the following to it:
name = Hello
description = Display a hello message to users
core = 7.x
version = 7.x-1.0
project = hello

  1. Create a file called hello.module

Getting started in Drupal 8

Setup a module

To setup the skeleton of the module:

  1. Go to your sites sites/all/modules or modules directory
  2. Create a directory called hello
  3. Create a file in the hello directory called hello.info.yml and add the following to it:
name: Hello
description: Display a hello message to users
package: Custom
type: module
version: 1.0
core: 8.x

  1. Create a new directory in the module’s src folder called Plugin. This is where you can store all of your plugins for a module.
  2. Create a folder called Block. A block is a plugin type.
  3. Inside the Block folder, create a file called HelloBlock.php.

In that file, we need to define the namespace and class. If you use an IDE like PHPStorm, this will be created automatically.

namespace Drupal\hello\Plugin\Block;

class HelloBlock {

} 

Register the block in Drupal 7

The first thing you need to do is to tell Drupal about the blocks that this module defines. You do that in Drupal 7 by implementing hook_block_info().

/**
 * Implements hook_block_info().
 */
function hello_block_info() {
  $blocks = array();
  $blocks['hello_block'] = array(
    'info' => t('Hello block'),
    'cache' => DRUPAL_NO_CACHE,
  );

  return $blocks;
}

Drupal expects the function to return an associative array, with the key being unique to the module. The key is otherwise know as the “delta”. If you visit the block table in the database, you will notice that there is a module column and delta column. The module column contains the module’s machine name.

Note: Cache is also included in this example. It is set to DRUPAL_NO_CACHE, which means that the block will not get cached. This is helpful in this example because you can see the block and any changes without having to clear cache.

Register the block in Drupal 8

Another new concept to Drupal that we need to use for the block is Annotations. In order for Drupal to find your block code, you need to implement a code comment in a specific way, called an Annotation. An Annotation provides basic details of the block such as an id and admin label. The admin label will appear on the block listing page.

Add the following comment, above class HelloBlock :

/**
 * Provides a 'Hello' Block
 *
 * @Block(
 *   id = "hello_block",
 *   admin_label = @Translation("Hello block"),
 * )
 */

Display the block in Drupal 7

To display the block itself, we need to implement hook_block_view(), which takes one argument: the block name (delta).

/**
 * Implements hook_block_view().
 */
function hello_block_view($block_name = '') {
  if ($block_name == 'hello_block') {
    $content = variable_get('hello_block_content', 'Hello world');
    $block = array(
      'subject' => t('Hello world'),
      'content' => t($content),
    );

    return $block;
  }
}

Firstly you need to check the block name in the 'if' statement for the relevant block. Then you create a block array with the subject and content. The subject will appear as the block title and the content will be the content itself.

The full Drupal 7 code:

/**
 * Implements hook_block_info().
 */
function hello_block_info() {
  $blocks = array();
  $blocks['hello_block'] = array(
    'info' => t('Hello block'),
    'cache' => DRUPAL_NO_CACHE,
  );

  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function hello_block_view($block_name = '') {
  if ($block_name == 'hello_block') {
    $content = variable_get('hello_block_content', 'Hello world');
    $block = array(
      'subject' => t('Hello world'),
      'content' => t($content),
    );

    return $block;
  }
}

Add opening PHP tags to the file. Due to limitations with the syntax highlighter, I can't include them here.

Display the block in Drupal 8

You need to extend the BlockCase class. To do that, add the following use statement below the namespace statement:

use Drupal\Core\Block\BlockBase;

And then change class HelloBlock to HelloBlock extends BlockBase:

namespace Drupal\hello\Plugin\Block;
use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'Hello' Block
 *
 * @Block(
 *   id = "hello_block",
 *   admin_label = @Translation("Hello block"),
 * )
 */
class HelloBlock extends BlockBase {

} 

Add opening PHP tags to the file. Due to limitations with the syntax highlighter, I can't include them here.

Next, we need to inherit docs from the base class and add a build method, which will return the content of the block.

class HelloBlock extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    return array(
      '#markup' => $this->t('Hello, World!'),
    );
  }
}

The build method will provide the output for the block. In other words, when you view the block on the site, it will display the string "Hello, World'.

Now that you have defined the block, head over to the blocks page (/admin/structure/block). On the right hand side, you will see the block listed under the heading Hello. Add it to a region like Sidebar first. And now you should see the block in the left side bar!

The full code for HelloBlock.php

/**
 * @file
 * Contains \Drupal\hello\Plugin\Block\HelloBlock.
 */

namespace Drupal\hello\Plugin\Block;
use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'Hello' Block
 *
 * @Block(
 *   id = "hello_block",
 *   admin_label = @Translation("Hello block"),
 * )
 */
class HelloBlock extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    return array(
      '#markup' => $this->t('Hello, World!'),
    );
  }
}

Add opening PHP tags to the file. Due to limitations with the syntax highlighter, I can't include them here.

Drupal 7 - configurable block

In Drupal 7, to make a block configurable in the admin interface so that an editor can change the block text, you need to implement hook_block_configure(). Here is an example:

/**
 * Implements hook_block_configure().
 */
function hello_block_configure($block_name = '') {
  $form = array();
  if ($block_name == 'hello_block') {
    $form['hello_block_content'] = array(
      '#type' => 'textarea',
      '#title' => t('Block contents'),
      '#size' => 60,
      '#description' => t('This text will appear in the first block.'),
      '#default_value' => variable_get('hello_block', t('Hello world')),
    );
  }
  return $form;
}

Note that the default value for the form is a variable_get(). This is set in the submit function:

/**
 * Implements hook_block_save().
 */
function hello_block_save($block_name = '', $edit = array()) {
  if ($block_name == 'hello_block') {
    variable_set('hello_block_content', $edit['hello_block_content']);
  }
}

The full code becomes:

/**
 * Implements hook_block_info().
 */
function hello_block_info() {
  $blocks = array();
  $blocks['hello_block'] = array(
    'info' => t('Hello block'),
    'cache' => DRUPAL_NO_CACHE,
  );

  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function hello_block_view($block_name = '') {
  if ($block_name == 'hello_block') {
    $content = variable_get('hello_block_content', 'Hello world');
    $block = array(
      'subject' => t('Hello world'),
      'content' => t($content),
    );

    return $block;
  }
}

/**
 * Implements hook_block_configure().
 */
function hello_block_configure($block_name = '') {
  $form = array();
  if ($block_name == 'hello_block') {
    $form['hello_block_content'] = array(
      '#type' => 'textarea',
      '#title' => t('Block contents'),
      '#size' => 60,
      '#description' => t('This text will appear in the first block.'),
      '#default_value' => variable_get('hello_block', t('Hello world')),
    );
  }
  return $form;
}

/**
 * Implements hook_block_save().
 */
function hello_block_save($block_name = '', $edit = array()) {
  if ($block_name == 'hello_block') {
    variable_set('hello_block_content', $edit['hello_block_content']);
  }
}

Add opening PHP tags to the file. Due to limitations with the syntax highlighter, I can't include them here.

Drupal 8 - configurable block

To make a block configurable in Drupal 8, you need to create a setting form. You can do this by adding methods to the WelcomeBlock class. There are three parts to this that you need to add:

  • Default message: This will ensure that if block form (which we are about to create) is not configured with a different message, the block will still display Hello world.
  • The form: The form that will allow selected users to add and edit the message
  • Submit method: The code to process and save the message from the form.

Default Configuration

The BlockBase abstract class contains a method called defaultConfiguration(). You are going to override this method to provide a default for the block form. Add the following method to the WelcomeBlock class.

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return array(
      'hello_block_content' => 'Hello world',
    );
  }

The configuration form

Next you need to define the configuration form. To do this, define a method called blockForm, which overrides the method in the BlockBase class.

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {
    $form['hello_block_content'] = array(
      '#type' => 'textarea',
      '#title' => $this->t('Hello message'),
      '#default_value' => $this->configuration['hello_block_content'],
    );

    return $form;
  }

In the above example, notice the #default_value. This ensures that when you view the form for the first time, you will see the default message. And if you change the message and submit the form, you will then see the changed message.

Let’s break this down and see what each part is doing. 

$this->configuration['hello_block_content']

This initialises the config variable. "hello_block_content'' is the module's configuration name, so this will load the admin settings.  

There is just one element to this form, the welcome message textarea:

    $form['hello_block_content'] = array(
      '#type' => 'textarea',
      '#title' => t('Hello message'),
      '#default_value' => $this->configuration['hello_block_content'],
    );

The title and description are returned from the form. 

Form Submit

You also need to implement the blockSubmit method, which will change the block configuration when the form is submitted.

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    $this->configuration['hello_block_content'] = $form_state->getValue('hello_block_content');
  }

Change the build method

And finally, you need to update the build method to use the hello_block_content configuration item.

    public function build() {
      return array(
        '#markup' => $this->t($this->configuration['hello_block_content']),   );
    }

Rather than just displaying a static hello, world string, you are now passing in the message from the configuration item through the $this->t() method.

Full code

The full code becomes:

/**
 * @file
 * Contains \Drupal\hello\Plugin\Block\HelloBlock.
 */

namespace Drupal\hello\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;

/**
 * Provides a 'Hello' Block
 *
 * @Block(
 *   id = "hello_block",
 *   admin_label = @Translation("Hello block"),
 * )
 */
class HelloBlock extends BlockBase {
  /**
   * {@inheritdoc}
   */
  public function build() {
    return array(
      '#markup' => $this->t($this->configuration['hello_block_content']),
    );
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return array(
      'hello_block_content' => 'Hello world',
    );
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {
    $form['hello_block_content'] = array(
      '#type' => 'textarea',
      '#title' => $this->t('Hello message'),
      '#default_value' => $this->configuration['hello_block_content'],
    );

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    $this->configuration['hello_block_content'] = $form_state->getValue('hello_block_content');
  }

}

Add opening PHP tags to the file. Due to limitations with the syntax highlighter, I can't include them here.

Overview of the changes

Drupal 7 Drupal 8 Register the block hook_block_info() @Block annotation Generate block content hook_block_view() build() method Configure block content hook_block_configure() and hook_block_save() blockForm() and blockSubmit() methods

Outro

Blocks are a great example of architectural changes between Drupal 7 and 8. Hooks have been replaced with a more robust and maintainable Plugin system.

Dec 22 2016
Dec 22

Annotations are another one of the new concepts in Drupal 8 to wrap your head around. Annotations are written as PHP code comments above a class or function and contain metadata about the function or class.

Annotations are used by other PHP projects. For example, Symfony2 uses annotations for routing rules and Doctrine uses them for ORM related meta data.

Annotations used by D8’s Plugin system

As it stands, Annotations are only used by the Plugin system in Drupal 8. They provide the mechanism for which Plugins are discovered by Drupal and describe their meta data.

Plugin discovery

One of the major conceptual changes in Drupal 8 is that Drupal is not aware of your code until you specifically tell it about your code.

In module development you tell Drupal about the module in a info.yml file. You tell Drupal about specific routes in route YAML files. You tell Drupal which menu links to add to the menu system in menu YAML files. Annotations are a similar concept for Plugins. Rather than having a YAML file for each plugin, you have an annotation and Drupal will automatically find (discover) the plugin.

Example plugin types

Blocks are the most common type of plugin in Drupal 8. But they are by no means the only type. Plugins include:

  • Blocks
  • Field formatters
  • Views plugins
  • Conditions
  • Migrate source

See Drupal.org for more information on Annotation-based plugins.

Example Annotation

Here is a quick example of an annotation for a block class:

/**
 * Provides a 'Welcome' Block
 *
 * @Block(
 *   id = "welcome_block",
 *   admin_label = "Welcome block",
 * )
 */

Block manager scans files looking for @Block annotation tag to find block instances.

The @Block annotation ensures that the block manager discovers this custom block and makes it available to be added to a region. The id of welcome_block is the machine name and the admin_label (Hello block) will appear in the block interface’s list of blocks.

Annotation parser

Annotations are read and parsed at runtime by an annotation engine. Drupal 8 uses the Doctrine annotation parser, which turns it into an object that PHP can use.

How is this different to Drupal 7?

In Drupal 7, meta data live in info hooks.

Let’s have a look at a custom block in Drupal 7 to demonstrate:

/**
 * Implements hook_block_info().
 */
function first_module_block_info() {
  $blocks = array();
  $blocks['first_block'] = array(
    'info' => t('First block'),
    'cache' => DRUPAL_NO_CACHE,
  );

  return $blocks;
}

/**
 * Implements hook_block_view().
 */
function first_module_block_view($block_name = '') {
  if ($block_name == 'first_block') {
    $content = variable_get('first_block_content', 'Some content');
    $block = array(
      'subject' => t('Sample block'),
      'content' => t(‘Sample block content’),
    );

    return $block;
  }
}

The block is registered with Drupal in first_module_block_info(), which implements hook_block_info(). Without this, Drupal wouldn’t know that the block exists and it would not appear in the block admin screen.

The problem with this approach is that .module files need to be read and stored in memory at runtime, using a more than ideal amount of memory.

Advantages of annotations

Performance improvement

Annotations use less memory than the D7 way and alternatives in D8.

In Drupal 7, each module’s .module file is read into memory for every request. In Drupal 8, an alternative to annotations is to have a getInfo method for each plugin class. But this would mean the class has to be loaded into memory to get this information and the memory is not freed up until the end of the request. This increases the amount of memory needed.

Annotations are tokenised and do not use as much memory. They are only loaded into memory and instantiated when their functionality is needed. Also, docblocks are cached by an opcode cache.

Live in the same file

Annotations live in the same file as the class that implements a plugin, so they are easier to find.

Drupal Annotation Syntax

Drupal’s Annotation syntax is heavily borrowed from Doctrines (but it is not exactly the same).

Annotations comprise of nested key value pairs and support nesting.

@Plugin

The annotation comment has to start with @Plugin, where “Plugin” is the type of plugin. In the example above, @Block was used because the annotation is for a plugin of type block.

keys

Keys on the root level should either have double quotes or no quotes at all. The general recommendation is to skip quotes unless the key name contains spaces.

In the block example above, there are two keys, id and admin_label. Neither of these have quotes.

Sub level keys must use double quotes.

Translatable text

Translatable strings should be wrapped in @Translation().

Values

  • strings - double quotes
  • lists - curly brackets
  • quotes within a string: use double quotes
  • maps - use curly brackets and equality sign to separate the key and the value
  • booleans - TRUE and FALSE (no quotes)
  • numbers - no quotes

Summary

  • Most info hooks replaced with annotations
  • Drupal’s annotation syntax borrowed from Doctrine
  • Can be used for any purpose - Drupal 8 currently uses for plugin system
  • Lives in the same file as the class that implements a plugin and is therefore easier to find
  • Uses less memory than the Drupal 7 method and raw PHP alternatives in Drupal 8
Nov 30 2016
Nov 30

A few weeks ago I wrote about routes and controllers in Drupal 8 and making them a route dynamic. This week we are going to look at adding a link to Drupal 8’s menu system in a custom module.

Before we get into the menu system, let’s take a few moments to recap on routes.

What is a route?

A route determines which code should be run to generate the response when a URI is requested. It does this by mapping a URI to a controller class and method. This defines how Drupal deals with a specific URI. Routes are based on Symfony’s routing system. Routes are defined in their own YAML file in the root of the module folder and take the format modulename.routing.yml

What a route doesn’t do is add a link to the menu system for that URI. In Drupal 8, that is decoupled from the route registration.

How is this different to Drupal 7?

In Drupal 7, you add a route and menu item in the same place, in hook menu.

This is how it looks in Drupal 7:

<?php

/**
 * Implements hook_menu(). 
 */
function hello_menu() {
  $items['hello/custom'] = array(
    'title' => 'Custom page',
    'page callback' => ‘hello_custom', 
    'access arguments' => array('access content'),
  );

  return $items;
}

The above code snippet will register the URL hello/custom, map it to a callback function that will generate the content and add it to the menu system.

Create a menu link

To demonstrate the Drupal 8 menu system, we will add a menu link to the main navigation menu.

Before we do that, we need to know the name of the menu to add the menu link to. If you head over to Structure -> Menus in a Drupal 8 site, you will see existing menu’s.

Main navigation is the one that we want. Click on Edit menu and you’ll see the links already defined for the menu. If this is a fresh install, it will probably just be Home.

Have a look at the path for this page:

admin/structure/menu/manage/main

The last part is the name of the menu, main. We will need this when adding a menu link to this.

With that out of the way, let’s add the menu link.

  • In your module root, create hello.links.menu.yml
  • Add the following:
hello.demo:
  title: 'Hello'
  description: 'Hello page'
  parent: main
  menu_name: main
  route_name: hello.content`

Let’s break this down.

  • hello.demo - the namespace for this menu link.
  • title - this will appear as the menu link
  • description - this will appear as the menu link title, a tooltip and/or the description in the admin UI.
  • parent - the parent item, which is used if you are adding this item as a child of another in a hierarchy. In this case the parent is the menu itself, so we are using the menu_name.
  • menu_name - this is the menu name we found in the previous step._
  • route_name - the route to call for this menu link.

You will need to clear the cache in order for the menu item to appear.

If you want to nest the menu link under another menu link, then use the parent key and assign it the namespace of the parent menu link. As an example, let’s nest this menu link under the existing Home menu link.

hello.demo:
  title: 'Hello'
  description: 'Hello page'
  parent: standard.front_page
  menu_name: main
  route_name: hello.content

You are probably wondering where I got the standard.front_page from for the Home menu link. There are two ways to find names for parent links.

  1. Look through module’s *.links.menu.yml files
  2. Inspect the URL of the menu link

Finding the appropriate .links.menu.yml can be tricky. The easiest way is to do a search through the code base. If I do a find for title: 'Home' then I’ll find the menu links YAML file at core/profiles/standard/standard.links.menu.yml.

Inspecting the URL of the menu link is far easier (let me know in the comments below if you have a better method). If you click on the edit button along side the Home menu link, and check out the path:

admin/structure/menu/link/standard.front_page/edit

The menu link namespace is part of the path. This is made up of the module name (standard) and the menu link name (front_page), separated with a dot.

Go back and look at the main menu admin screen. You will see that the Hello link is now nested under Home.

Admin menu

If you have an admin form, you might want your form to be listed in the Configuration section of the admin interface. To do that, the parent will be one of the menu links listed under Administration. Just like above, you can find the name spaced menu name via the menu interface.

Under Structure > Menu’s, click on Edit Menu for the Administration link.

Scroll down until you see the Development link, which is nested under Configuration. Click on edit. The path should be:

admin/structure/menu/link/system.admin_config_development/edit

So in this case, the name of the parent will be system.admin_config_development.

hello.admin:
  title: 'Hello module settings'
  description: 'Module settings form'
  parent: system.admin_config_development
  route_name: hello.content
  weight: 100

To make it easier, here is a list of the main menu links under admin configuration that you can use.

  • People: user.admin_index
  • System: system.admin_config_system
  • Content authoring: system.admin_config_content
  • User Interface: system.admin_config_ui
  • Media: system.admin_config_media
  • Search and metadata: system.admin_config_search
  • Regional and language: system.admin_config_regional
  • Web services: system.admin_config_services
  • Workflow: system.admin_config_workflow

Complete code

Here is the full code for this example. If you have already been following along with previous tutorials in this series and created the hello module, you could skips to step 5 and 6.

Step 1: Create a new directory in the modules directory called hello.

Step 2: In the hello directory, create a file called hello.info.yml and add the following code to that file:

name: Hello
description: An experimental module to build our first Drupal 8 module
package: Custom
type: module
version: 1.0
core: 8.x

Step 3: In the hello directory, create a file for the route called hello.routing.yml and add the following code to that file:

hello.content:
  path: '/hello/{name}'
  defaults:
    _controller: 'Drupal\hello\Controller\HelloController::content'
    _title: 'Hello world'
    name: 'there'
  requirements:
    _permission: 'access content'

Step 4: In the hello directory, create a new directory called src and inside that create a directory called Controller. In the Controller directory, create a file for the controller called HelloController.php and add the following code to it. Add open PHP tags to the top of the file (I currently can't add this to the code snippet as it breaks the code highlighter).

<?php

/**
 * @file
 * Contains \Drupal\hello\Controller\HelloController.
 */
namespace Drupal\hello\Controller;

use Drupal\Core\Controller\ControllerBase;

class HelloController extends ControllerBase {
  public function content($name) {
    return array(
      '#type' => 'markup',
      '#markup' => $this->t('Hello @name', array('@name' => $name)),
    );
  }
}

Step 5: In the hello directory, create a file for the menu links called hello.links.menu.yml and add the following code to it:

 hello.demo:
   title: 'Hello'
   description: 'Hello page'
   parent: main
   menu_name: main
   route_name: hello.content

Step 6: Optionally change the menu link and nest it under the Home menu link:

 hello.demo:
   title: 'Hello'
   description: 'Hello page'
   parent: standard.front_page
   menu_name: main
   route_name: hello.content

Step 7: Clear the cache


In most cases, you would create a menu link in a custom module for an admin form. In a few week’s, we’ll be looking at creating admin forms in Drupal 8 and creating a menu link for it. Jump on the newsletter if you want to be the first to get it.

Nov 02 2016
Nov 02

In the last tutorial in this series, Understanding routes and controllers, we looked at the two fundamental steps to programmatically define a custom page in Drupal 8. Today we are going to take this one step further and add a parameter to the route, so that it is dynamic.

Go back to the route defined in the Understanding routes and controllers tutorial. This is what it looks like:


  1. hello.content:

  2. path: '/hello'

  3. defaults:

  4. _controller: 'Drupal\hello\Controller\HelloController::content'

  5. _title: 'Hello world'

  6. requirements:

  7. _permission: 'access content'

With the above code, when you go to /hello, you will get a ’hello world’ message from the controller.

We are about to change the code so we can replace world in hello world with a name that we pass in via the URL. If you hit /hello/bob, the message will become hello bob, hello/alice will become hello alice and so on.

You are going to change this so that you can pass in a person’s name as a parameter.

You can add the name to the line that has the path key:


  1. path: '/hello/{name}'

In the above snippet, {name} is known as the slug and acts as a placeholder. You can name a slug what ever you like, as long as you surround it with accolades ({}). The same name should then be used as an argument in the controller (you’ll see that in a moment). The value will be passed into the controller. In other words, if you hit /hello/alice, then alice is the value for name and alice will be passed to the $name variable in the controller.

The full route will become:


  1. hello.content:

  2. path: '/hello/{name}'

  3. defaults:

  4. _controller: 'Drupal\hello\Controller\HelloController::content'

  5. _title: 'Hello world'

  6. requirements:

  7. _permission: 'access content'

Default name

There needs to be a default name, just in case the name is not passed in the URL. This allows you to hit /hello without the name added. To do that, you can add the default name to the defaults part of the route Yaml file.

The default can be whatever you want. I’m going to go with “world”, so that if in the absence of a name, the page content will be “hello world”.

Add the following default name to the route Yaml file:


  1. name: 'world'

So the complete route Yaml file becomes:


  1. hello.content:

  2. path: '/hello/{name}'

  3. defaults:

  4. _controller: 'Drupal\hello\Controller\HelloController::content'

  5. _title: 'Hello world'

  6. name: 'world'

  7. requirements:

  8. _permission: 'access content'

Add the name to the controller

Now that the route can take the name parameter, you need to use it in the controller for it to take effect.

Take another look at the controller from the previous tutorial:


  1. <?php

  2. /**

  3.  * @file

  4.  * Contains \Drupal\hello\Controller\HelloController.

  5.  */

  6. namespace Drupal\hello\Controller;

  7. use Drupal\Core\Controller\ControllerBase;

  8. class HelloController extends ControllerBase {

  9. public function content() {

  10. '#type' => 'markup',

  11. '#markup' => t('Hello world'),

  12. );

  13. }

  14. }

The name is now available to the controller as a variable. The variable is called $name.


  1. public function content()

will become


  1. public function content($name)

To use the variable, you can pass it into the t(). The is the same as it is in Drupal 7.


  1. '#markup' => t('Hello world')

will become


  1. '#markup' => t('Hello @name', array('@name' => $name))

The full controller is now:


  1. <?php

  2. /**

  3.  * @file

  4.  * Contains \Drupal\hello\Controller\HelloController.

  5.  */

  6. namespace Drupal\hello\Controller;

  7. use Drupal\Core\Controller\ControllerBase;

  8. class HelloController extends ControllerBase {

  9. public function content($name) {

  10. '#type' => 'markup',

  11. '#markup' => t('Hello @name', array('@name' => $name)),
  12. );

  13. }

  14. }

And that is all there is to it. You can now add any name you like to the URL and that will appear as part of the hello message.


  1. /welcome/bob will display hello bob

  2. /welcome/alice will display hello alice

  3. /welcome/jane will display hello jane

  4. /welcome will display hello there (there is the default)

Complete code

Here is the full code for the hello module.

In hello.routing.yml:


  1. hello.content:

  2. path: '/hello/{name}'

  3. defaults:

  4. _controller: 'Drupal\hello\Controller\HelloController::content'

  5. _title: 'Hello world'

  6. name: 'world'

  7. requirements:

  8. _permission: 'access content'

In src/Controller/HelloController.php:


  1. <?php

  2. /**

  3.  * @file

  4.  * Contains \Drupal\hello\Controller\HelloController.

  5.  */

  6. namespace Drupal\hello\Controller;

  7. use Drupal\Core\Controller\ControllerBase;

  8. class HelloController extends ControllerBase {

  9. public function content($name) {

  10. '#type' => 'markup',

  11. '#markup' => $this->t('Hello @name', array('@name' => $name)),
  12. );

  13. }

  14. }

And if you haven’t followed along with the previous tutorial, you’ll need a .info file.

In hello.info.yml:


  1. name: Hello

  2. description: An experimental module to build our first Drupal 8 module

  3. package: Custom

  4. type: module

  5. version: 1.0

  6. core: 8.x

Oct 19 2016
Oct 19

Two weeks ago I wrote about routes and controllers in the introduction to namespaces. This week we are going to take a much closer look at routes and controllers. 

If you missed out on the namespace introduction, check it out here: A gentle introduction to namespaces.

So, what exactly is a route and a controller?

When you create a custom page in Drupal with code, you need both a route and a controller. You define the URL for the page with the route. And then you create a controller for that page. This will be responsible for building and returning the content for the page.

Routes

A route determines which code should be run to generate the response when a URI is requested. It does this by mapping a URI to a controller class and method. This defines how Drupal deals with a specific URI.

Routes are stored in a YAML file (check out last week's tutorials on YAML files) in the root of the module and use the following naming convention:


  1. modulename.routing.yml

Here is the example from the namespace tutorial:


  1. hello.content:

  2. path: '/hello'

  3. defaults:

  4. _controller: 'Drupal\hello\Controller\HelloController::content'

  5. _title: 'Hello world'

  6. requirements:

  7. _permission: 'access content'

The name of the route is hello.content. The path is /hello, which is the registered URL path. So when a user enters sitename.com/hello, this route will decide which code should be run to generate a response.

We then have two default configurations specified, the controller and the title.

The controller tells Drupal which method to call when someone goes to the URL for the page (which is defined in the route).

Let’s take a closer look at the path included in _controller.


  1. 'Drupal\hello\Controller\FirstController::content’

This comprises of a namespaces class, a double colon and then the method to call.

Drupal route with a namespace, class and method

The title is the default page title. So when a user goes to /hello, the page title will be ‘Hello world’.

Route title maps to the page title

Controllers

Controllers take requests or information from the user and decide how to handle the request. For this module, the controller is responsible generating the content (the ‘Hello world’ message) and returning it for the page.

A controller that returns a simple Hello world message looks like this:


  1. <?php

  2. /**

  3.  * @file

  4.  * Contains \Drupal\hello\Controller\HelloController.

  5.  */

  6. namespace Drupal\hello\Controller;

  7. use Drupal\Core\Controller\ControllerBase;

  8. class HelloController extends ControllerBase {

  9. public function content() {

  10. '#type' => 'markup',

  11. '#markup' => t('Hello world'),

  12. );

  13. }

  14. }

The controller is returning a renderable array with “Hello world”. So if you hit /hello once all this is in place, you will get a Drupal page with a Hello world message.

Drupal 8 controller content method maps to content on the page

Drupal 8 module route and controller

The controller lives in a Controller directory within the the src directory of a module.

The controller class in the src controller directory

Putting all of this together

To see this in action, you’re going to combine this all in a new module.

  1. In the /module directory, create a directory called hello
  2. Create hello.info.yml in the root of the hello directory
  3. Add the following to hello.info.yml:

  1. name: Hello

  2. description: An experimental module to build our first Drupal 8 module

  3. package: Custom

  4. type: module

  5. version: 1.0

  6. core: 8.x

  1. Create a directory inside the hello module directory called src, which is short for source
  2. Create a directory within src called Controller
  3. Within the Controller directory, create a file called HelloController.php
  4. Add the following to HelloController.php:

  1. <?php

  2. /**

  3. * @file

  4. * Contains \Drupal\hello\Controller\HelloController.

  5. */

  6. namespace Drupal\hello\Controller;

  7. use Drupal\Core\Controller\ControllerBase;

  8. class HelloController extends ControllerBase {

  9. public function content() {

  10. return array(

  11. '#type' => 'markup',

  12. '#markup' => t('Hello world'),

  13. );

  14. }

  15. }

  1. Create a file called hello.routing.yml in the root of the hello module directory
  2. Add the following code to hello.routing.yml:

  1. hello.content:

  2. path: '/hello'

  3. defaults:

  4. _controller: 'Drupal\hello\Controller\HelloController::content'

  5. _title: 'Hello world'

  6. requirements:

  7. _permission: 'access content'

  1. Enable the hello module
  2. Visit /hello to see the Hello world message

It is good practice to complete these steps manually when you are first learning the concepts in Drupal 8 development, but you can save time by using the Drupal Console to create most of the code in the above steps. To find out how, check out my 7 day email course on the Drupal Console.

Oct 11 2016
Oct 11

YAML is a data serialisation format that is both powerful and easy for us humans to read and understand. In Drupal 8 it's used where Drupal needs a list but doesn’t need to execute PHP. One of the reasons why YAML was chosen for Drupal 8 is because it is already used in Symfony.

In last week’s introduction to namespacing tutorial, you saw YAML files used for the module info file and the route.

They are also used in Drupal 8 to replace info hooks in modules, declaring services, permissions and in the configuration management system.

A YAML file comprises of key and value pairs, separated with a colon. This is how a single line from a YAML file looks:


  1. name: Welcome

The key and value pair for this example is:

Key value pair in a YAML file

YAML files replaces .info files

In Drupal 7, .info files were used to provide information to Drupal about modules and themes. This information includes its human readable name, machine name, description, version number, any dependencies and so on. Without a .info file, Drupal wouldn't know that a module or theme existed.

Drupal 8 uses YAML (.yml) files instead of .info files.

Here is an example of the code in a YAML file. This is the code for the info.yml file for a module.


  1. name: Welcome

  2. type: module

  3. description: Display a message when a user logs in

  4. core: 8.x

  5. package: Custom

If you have done any Drupal 7 development, the information will look familiar. One of the key differences between Drupal 7 and 8 is that the key value pair are separated with an equals sign (“=“) in the INI format used in Drupal 7, and colon (“:”) in the YAML format used in Drupal 8. There needs to be a space after the colon.

YAML files define routes

In Drupal 8, YAML files do more than just provide information about modules and themes. As mentioned above, they are also used to define menu items, tabs, routes, services and so on.

Here is the example from last week’s email of the hello module route hello.routing.yml:


  1. hello.content:

  2. path: '/hello'

  3. defaults:

  4. _controller: 'Drupal\hello\Controller\FirstController::content'

  5. _title: 'Hello world'

  6. requirements:

  7. _permission: 'access content'

Hierarchy

In the example above, some of the lines are indented. This is because YAML files support hierarchy via indentation.

Here is another example:


  1. first_module.admin:

  2. title: 'First module settings'

  3. description: 'A basic module to return hello world'

  4. parent: system.admin_config_development

  5. route_name: first_module.content

  6. weight: 100

In the above YAML, all lines below first_module.admin are indented, which means they are nested under first_module.admin. In YAML, this is called a compound data type, which sounds scary, but it a lot like an array in PHP. It means that everything listed under first_module.admin are children of it.

This would be the equivalent of the following PHP array:


  1. 'first_module.admin' => array(
  2. 'name' => 'First module settings',

  3. 'description' => 'A basic module to return hello world',

  4. 'parent' => 'system.admin_config_development',

  5. 'route_name' => 'first_module.content',

  6. 'weight' => '100',

  7. ),

  8. );

Hopefully it provides you with some context around YAML files. You’ll end up using YAML files a lot as you embark on Drupal 8 development.

Sep 29 2016
Sep 29

Drupal 8 represents a major shift away from “Drupalisms” to mainstream object-orientated PHP. Experienced developers who are used to other frameworks like Symfony, should find it much easier to pick up Drupal now than they would have in Drupal 7 and previous versions. But this shift presents challenges for those of who are used to the Drupal 7 way of doing things and aren’t necessarily up to speed with all the new PHP best practices.

Over the next few weeks, I’ll be sharing tutorials on some of the key concepts you need to understand in order to master Drupal 8 module development. This week, we are going to look at namespaces.

The Drupal 7 way

In Drupal 7, all functions in the system had to have a unique name or you would get a fatal error. If the name was not unique, then the system would not know which function code to use when the function is called in another function.

Module developers can’t rely on other functions not having the same name that they use. So to get around this, it is common practice to start each function name in Drupal 7 with the same name as the module. This makes the function name unique when if another module uses the same name.

But having to use these unique names for functions is far from ideal. For starters, it makes function names longer that they would otherwise be.

Drupal 8 and namespaces

With the introduction of Drupal 8, we can now use a different method for making our code unique. Drupal 8 extensively uses PHP classes instead of simple functions. Two classes can have the same name as they have a different namespace.

You can think of it as a way of organising classes into folders and subfolders and the namespace as the path to the file. If you try and create the same file with the same name in the same folder, you will get an error. You could change the name of the file, but if you really want to keep the same file name, you would most likely create a sub folder and put the file in there. This makes the file unique because it has a different path. Namespacing works in the same way.

Let’s have a look at an example. We’ll start off by creating a basic module to illustrate how namespaces work. If you already have a module and just want an explanation of namespaces, jump to the namespace section.

Setup a basic module

You are going to create a module called hello for demonstration purposes. In the /module folder, create a directory called hello.

Create an info .yml file

The info .yml file tells Drupal that your module exists and provides important information. This is similar to creating a .info file in Drupal 7.

The filename should be the machine name of your module with the .info.yml extension. In this case, it will be hello.info.yml.

  • Create hello.info.yml in the root of the hello directory.

  1. name: Hello

  2. description: An experimental module to build our first Drupal 8 module

  3. package: Custom

  4. type: module

  5. version: 1.0

  6. core: 8.x

Create the src directory

In Drupal 8, most modules can have controllers, plugins, forms, templates and/or tests. These are stored in a folder called src, which is short for source.

  • Create a folder inside the hello module folder called src, which is short for source.

Create a basic controller

We need a page controller to output content to the page.

Here are the steps to create the basic controller for the module:

  • Create a folder within src called Controller.
  • Within the Controller folder, create a file called HelloController.php.

In HelloController.php, we will create a simple “hello world” message so that we know it is working.


  1. <?php

  2. /**


  3.  * @file


  4.  * Contains \Drupal\hello\Controller\HelloController.


  5.  */

  6. namespace Drupal\hello\Controller;

  7. use Drupal\Core\Controller\ControllerBase;

  8. class HelloController extends ControllerBase {

  9. public function content() {

  10. '#type' => 'markup',

  11. '#markup' => t('Hello world'),

  12. );

  13. }

  14. }

Let's dive into the namespace line in this code.

Namespaces

You can see from the above code that the namespace has been defined as Drupal\hello\Controller.

You might be thinking that doesn’t exactly match the folder structure because if it did, the namespace would be Drupal\modules\hello\src\Controller.

The reason why modules\src is missing from the namespace is because namespaces for Drupal modules are automatically mapped to the module’s src folder. This is why it is critical that you follow this standard folder structure in modules(fn).

If you were to use this HelloController class in different namespaces, you would include it with the use keyword and its namespace.


  1. use Drupal\hello\Controller\HelloController

You can see this in action with the use statement in the example above. HelloController is extending another class called ControllerBase. In order to get access to that class, it has been added with it’s namespace via the following use statement:


  1. use Drupal\Core\Controller\ControllerBase;

If this use statement was not included, then PHP would be looking for ControllerBase in the current namespace (Drupal\hello\Controller). It wouldn’t find it there, so you would get a fatal error. So the use statement allows you make use of classes from other namespaces.

Add a route file

The controller we created above will not do anything at this stage. Let’s round this off by making it return the string when you hit a URL.

In order for a controller like this to be called and executed, it needs to map a route from the URL to the controller.

  • Create a file called hello.routing.yml in the root of the hello folder
  • Add the following code to hello.routing.yml

  1. hello.content:

  2. path: '/hello'

  3. defaults:

  4. _controller: 'Drupal\hello\Controller\HelloController::content'

  5. _title: 'Hello world'

  6. requirements:

  7. _permission: 'access content'

View the content

If you haven't already done so, enable the hello module and clear the cache. If you now go to /hello, you will see the Hello World message that is being returned from the controller.

Namespaces in a nutshell

A namespace is a way of organising your classes into virtual folders and ensures that your class does not conflict with another class with the same name (the class name can be the same but the namespace is unique). The use keyword allows you to use classes from other namespaces when you need them.

Aug 22 2016
Aug 22

One of the many changes in Drupal 8 is adding a block to a region. The block interface has been pretty consistent over the years, so changes to how it works can be confusing at first. You do something over and over again and then “Wait a minute! Things have moved, what do I do?!”. But never fear, the new way of adding blocks to regions is pretty straight forward once you get your head around it.

Create a new block

For this demonstration, we are going to create a new block in the blocks interface (you could also create a block in a custom module, or it could come from another module like Views).

In the admin menu, go to Structure and then Block layout.

The block layout button

You will then land on the main blocks interface page and see a list of all existing blocks and the regions that they belong to.

At the top of the page, you will see two tabs: Block layout and Custom block library. You are currently on the Block layout tab. Click on the Custom block library tab.

You are going to add your own module, so click on the + Add custom block button at the top of the modal. You will then get the add block form. Add the following for the description and body:

  • Description: Hello block
  • Body: Hello, World!

Save the form. You should then get a message to say that the block has been created.

The block you created should now appear on the list of custom blocks.

Next to Sidebar first you will see a Place block button.

Place block

You can add the new block to any region. For the purposes of this exercise, let’s add the block to the Sidebar first region.

Add the block to a region, head back over to the Block layout tab. To add the block to the sidebar first region, click on the Place block.

Place a block button for sidebar first region

A modal will appear with a list of available blocks. These blocks have already been defined in various modules or are custom modules (like the one you just created). You should see the Hello block block that you created above. If it is hard to spot in the list, you can search for it in the Filter by block name field at the top of the modal. To add the block to this region, click on the Place block. Then on the next form (Configure block), click Save

You should then see the block listed under Sidebar first.

New custom block in sidebar first region list

View the block

Go to the homepage of the site. You should now see the block you created in the sidebar left of your site.

The new custom block on the page

Assigning the block to another region

In another change in Drupal 8, you can now assign a block to more than one region. To demonstrate this, try adding the block you created to the Sidebar second region.

  • Go back to the main blocks interface
  • Next to sidebar second click on Place a block
  • In the filter by block name field, search for hello. The hello block you created above should be visible
  • In the operations column, click on place block.

Get more

There are many changes in Drupal 8. I’m going to sharing tutorials and guides to many of them over the coming weeks and months. Be the first to get them

Jul 29 2016
Jul 29

With a click here, and a click there, it can be really easy to mess up a Drupal site, especially if you are fairly new to it. It doesn't have to be you, in the hands of your manager or client, they can end up breaking the site. Setting the correct permissions will help reduce the risk of this, but sometimes people want more permission than they really should have. And when the site owner comes to you and says - “I broke all the things!” - you immediately wonder when the last backup was. Are backups even running? How do I restore from a backup! Help!

Fix all the things with one click

But you don’t have to live in fear of a broken Drupal site. With just one click, you can quickly and easily restore your Drupal site to its former glory. As soon as your client or boss says "I broke all the things", you can say "I just fixed all the things!". They will be amazed at your Drupal harnessing powers.

The ability to restore your Drupal site with one click comes from the Backup and Migrate module. Once you have set it up, it is as easy as pie to use.

Installing Backup and Migrate

Before you start installing and getting to know Backup and Migrate, I highly recommend you initially set it up on a non-critical site. Ideally a site you have running on your local computer. You don’t want to start testing restoring backups for the first time on a live site.

Head over to the [Backup and Migrate] project page to download and install. Or, if you are a Drush user, install with this command:


  1. $ drush en backup_migrate -y

Tip: you don’t need to download and enable modules with separate commands. En (alias of enable) will download the module if it doesn’t exist.

Configure Backup and Migrate

Head over to the Backup and Migrate settings page (Configuration > Backup and Migrate).

Quick backup

The first screen you will see is the Quick Backup screen. You can use this to quickly create a backup of your database. Let’s have a quick look at the options:

Backup my

You can choose to backup your default database, the files directory or the entire site. If you don’t have the code for your site in a source control system like Git, then it is a good idea to backup the entire site. If you do, you can stick with just the default database.

To

At the moment you can only download to download, which means it will download the database to your local computer. We will add more options to that in a moment.

Go ahead and download your default database to your local computer.

Restore

In the restore tab, you can upload the database you just downloaded and restore it. This means that the database your site is using will be replaced.

Save backups

In saved backups, there are no databases at the moment. This is because we have only downloaded the database to our local computer. We haven’t set it up to save in a place that Drupal understands (we will do that in a moment).

Schedules

This is where the fun begins. Rather than manually downloading the database every time, you can setup a backup schedule. This will automatically run the backup when Drupal runs cron.

Before you setup the schedule, you need to setup a backup destination. We will do that in the next step.

Settings

The first ting we need to do in the settings is to setup the backup destination, so that you can then setup a schedule. There is a submenu in the top right, click on Destinations.

You might see a warning that you must specify a private file system path.

Warning that you must specify a private file system path

Head on over to the file system settings (admin/config/media/file-system). In Private file system path, add the following path:

sites/default/files/private

Head back to the destinations settings. You will now see a Manual Backups Directory and Scheduled Backups Directory. This means that you can now backup your database and have it saved to the private file system path that you set above, rather than your local computer.

Schedules

Now that Backup and Migrate can save to Drupal’s private file system rather than your computer, you can setup a schedule. Head back to the Schedules tab and click Add Schedule.

  • Schedule name: Give your backup schedule a meaningful name
  • Backup Source: As above, if your code is already in source control, just backup the default database. Otherwise, it is a good idea to backup the Entire Site
  • Run using Drupal’s cron: You can set how often you want the backup to run.
  • Automatically delete old backups You can end up with a lot of backups, so it is a good idea to delete old ones.
  • Backup Destination We currently only have one backup destination, the schedule backups directory setup above.

Creating a new Drupal Backup and Migrate schedule

Test by running cron

To test the backups are working, run Drupal’s cron. Go to Configuration > Cron and hit the Run Cron button. Head back to the Backup and Migrate settings and to the Saved Backups
tab and you will see the backup you just saved.

Running Drupal cron

Testing the backups

To restore the saved backup, head over to the Restore tab and check Restore from a saved backup. You will see the scheduled saved backup appear.

Restore from a saved backup

Before you restore it, test that it is working. Create a new node with a title Testing Backups. This node will now exist in your current database, but not in the backup version. When you store it, it should disappear.

Click Restore Now to restore the database.

Check that the Testing Backups node no longer exists.

Restoring

There are two options to restore the database. The first option is to go to the Restore tab, as in the above step. Click on Restore from a saved backup and select the latest backup from the list.

The second, a slightly more straightforward option, is to click on the Saved Backups tab. This will take you directly to a list of the latest backups and from there, you can restore any database from the list simply by clicking the restore link.

The saved backups tab in Backup and Migrate

Blast off

That is all there is to setting up the backup schedule. Now if you, or someone else, breaks something on the site, you can easily revert the database by restoring a saved version with just one click.

Taking it further

This is a simple setup. You can take it further by:

Note on Drupal 8

The above steps and screen shots are for a Drupal 7 install. There is an alpha version of Backup and Migrate for Drupal 8, but it is not yet stable.

Jul 19 2016
Jul 19

In Drupal Views, you can wrap a field in a basic element (such as div, span, H1, H2, p, strong). But sometimes you are going to need to wrap a field in an HTML element that is not available in the list. For example, if you have an image field that and need to wrap in a <figure> element (this is the example we will use for this tutorial). This can be pretty confusing at first, but fortunately there are a few methods available to get the job done.

Wrapping in a basic HTML element

Before we start with the methods to wrap the field in any HTML element we want, let's have a look at how you use the basic elements available in Views by default. In a View, click on a field and you’ll see the Style Settings fieldset.

This example is an image field.

Open the Style Settings fieldset and you will see the first option - Customise field HTML

  • Tick Customize field HTML
  • Select DIV from the HTML element drop down

If you go and inspect the display of the View, you will see the div has been added with a class of field-content.


  1. <div class=“field-content”>
  2. <img src=“image.jpg" width="198" height="110">

You can take this further and add a class to the above div. Go back to the Style Settings for a field, select Customize field HTML and then Create a CSS class.

This will create the following HTML:


  1. <div class=“field-content figure”>
  2. <img src=“image.jpg" width="198" height="110">

So we can add any class to a field and use that for styling. But what we really want is to wrap the image field in a <figure> html element, like so:


  1. <figure>

  2. <img src=“image.jpg" width="198" height="110">
  3. </figure>

There is no option for figure in the HTML element dropdown.

There are two ways to achieve this.

  1. Create a template
  2. Rewrite the result

Let’s take a look at how to do both.

Method 1. Rewrite the result

The first method is to use the rewrite the result option in the field in Views.

  • In the Views Fields section, click on the field you want to wrap the HTML element around

  • Open the Rewrite Results fieldset

  • Tick Rewrite the output of this field (in Drupal 8, this is Override the output of this field with custom text)
  • In the Text field, add the opening and closing tags for the HTML element. e.g. <figure></figure>
  • Open Replacement Patterns and you should see a token for this field. Copy the token.
  • Paste the token in-between the opening and closing tags. In Drupal 7 this will look like: <figure>[field_news_blog_img]</figure>. In Drupal 8, this will look like: <figure>{{ field_image }}</figure>

  • Click Apply
  • Save the View

And now if you look at the result, the field will be wrapped in the

<

figure> element.

Method 2. Create a template

The second approach is to create a template for the field and add that to your theme. This will output the result of the field and you can add any HTML you like to the template.

Steps to create the template:

  • Open the Advanced fieldset on the righthand side of the View
  • Click on Theme: Information, which is second from the bottom

  • Click on the field that you want to wrap the HTML element around from the list of possible templates. If it is an image field called field_image, then template will be Field Content: Image (ID: field_image) You will see the following:

  1. <?php

  2. /**

  3.  * @file

  4.  * This template is used to print a single field in a view.

  5.  *

  6.  * It is not actually used in default Views, as this is registered as a theme

  7.  * function which has better performance. For single overrides, the template is

  8.  * perfectly okay.

  9.  *

  10.  * Variables available:

  11.  * - $view: The view object

  12.  * - $field: The field handler object that can process the input

  13.  * - $row: The raw SQL result that can be used

  14.  * - $output: The processed output that will normally be used.

  15.  *

  16.  * When fetching output from the $row, this construct should be used:

  17.  * $data = $row->{$field->field_alias}

  18.  *

  19.  * The above will guarantee that you'll always get the correct data,

  20.  * regardless of any changes in the aliasing that might happen if

  21.  * the view is modified.

  22.  */

  23. ?>

  24. <?php print $output; ?>

  • Create a new file in your theme. You will find the name that it can use next to the field that you just clicked on. The one you choose depends on how specific you want to be. If you use the first one (views-view-field.tpl.php), the template will be used for all fields. If you use the last one (views-view-field--latest-articles--page--title.tpl.php), it will be used just for this field.

  • Copy and paste the code into the new file
  • Wrap the HTML element around <?php print $output; ?>:

  1. <figure>

  2. <?php print $output; ?>

  3. </figure>

  • Clear the cache
  • The field should now be wrapped in the <figure> element

I find the first approach (rewrite the result) an easier approach because you don’t need to mess around with creating template fields and you can do it all in the Views UI. As with a lot of things in Drupal, despite being confusing at first, Drupal is so flexible that there is generally a way to make it do what you need it to do.

May 19 2016
May 19

Developing custom modules in Drupal 8 is a daunting prospect for many. Whether you're still learning Drupal 7 module development or are more experienced, Drupal 8 represents a significant shift in the underlying architecture and the way modules are constructed.  

And now is the time to learn Drupal 8 module development. It is new, it is fresh and it brings a lot more power to sites of all sizes. Drupal has so many cutting edge features that would previously only have been available to enterprise applications. Drupal 8 is going to revolutionise the way we build websites. Now we have an enterprise level application that we can all use and benefit from - this is a huge boost for our clients and employers. 

If you are feeling anxious about it, the most important thing is to start somewhere. Once you start developing Drupal 8 modules, the new concepts will start to click. And getting started is not as difficult as you might fear. It can be made easier by a new tool - the Drupal Console. The Drupal Console can be used to generate a lot of the boilerplate code and files that you need to create Drupal 8 modules. 

Before we get started, let’s take a moment to thank everyone who has been involved in making Drupal 8 and the Drupal Console. It’s an exciting and fun time to be involved in Drupal. 

Let’s get started.  

What is the Drupal Console?

Drupal Console is a new Command Line Interface (CLI) for Drupal, built specifically for Drupal 8 (based on the Symfony Console component). In many ways, the Drupal Console is similar to Drush. You can use it to rebuild caches, connect to databases and install modules. Unlike Drush, Drupal Console can also be used to generate module files' boilerplate code out of the box with no other modules needed. This saves you a lot of time - giving you more time to focus on the business logic of your module. 

Installing

You need to run three commands to install the Drupal Console. 

  • Use curl to get drupal.phar: curl https://drupalconsole.com/installer -L -o drupal.phar
  • Move it to your local bin, so that it can be used anywhere on your system: mv drupal.phar /usr/local/bin/drupal
  • Make it executable: chmod +x /usr/local/bin/drupal

If you get stuck installing it, or want more information, check out the Drupal Console website. There are installation instructions for Mac/Linux here and Windows here

Run

Now you should be able to run the Drupal command. You will see a list of basic commands that you can run. To run it, simply run drupal in the command line. 

Run the Drupal Console

See available commands

Run drupal list to see available commands. You can make and install a whole new site with Drupal Console, but in this series, we are focused on creating new modules, so I’m going to assume you already have a Drupal 8 site up and running in the development environment of your choice. 

Shortcuts

Shortcuts in Drupal Console are like aliases in Drush and you can use them for some commands to save on time typing. For clarity during this series, I’m going to stick with the full commands. 

Drupal Console shortcuts

More commands in existing Drupal project

To use Drupal Console with your existing Drupal 8 site, simply change to that folder in the command line. When you use drupal list within a Drupal directory, you should see many more commands, including the generating modules and connecting to the database. 

In my case, my Drupal 8 demo site is in a folder called drupal8 inside the Sites folder. So I’ll run the following to get to the root of my Drupal 8 site:


  1. $ cd ~/Sites/drupal8

Generate module command

You can generate a new custom module with the generate:module command, or its shortcut gm. 

Generating the welcome module

During this series you are going to create a module called welcome. This module will do the following things:

  • Create a custom path and return a message
  • Display a message when users log in
  • Make the message configurable with an admin form
  • Create a custom block and make it configurable 

All you need to do to kick it off is run the generate:module command.


  1. $ drupal generate:module

Give the module a name of Welcome hit enter for all of the options to accept the default with the exception of:

  • Enter module description: Display a message when a user logs in
  • Do you want to generate a .module file: yes

You should then see the basic welcome folder in your site under /modules/custom with the following files:

  • composer.json
  • welcome.info.yml
  • welcome.module

The info file

The Drupal Console has created an info yml file to tell Drupal that your module exists. This is a required file and is similar to creating a .info file in Drupal 7.

Open welcome.info.yml and you will see that it contains the following:


  1. name: Welcome

  2. type: module

  3. description: Display a welcome message to users

  4. core: 8.x

  5. ​package: Custom

Install the module

The module now exists but it is not enabled. You don’t need to go to the module page to enable it, you can install and enable the welcome module using the module:install command:


  1. $ drupal module:install welcome

This will install the module and automatically rebuild the cache. 

The obligatory hello world page

Everyone seems to love a hello world page! So let’s create one. We want a page on a custom path that returns the message “Welcome!”.

To do that, we need two things:

  • A controller, which will return the message
  • A route, which will map a URL to the controller

You can use the Drupal Console to create both the controller and the route using the generate:controller command. 


  1. $ drupal generate:controller

Next, you will get a serious of prompts. Type in the following:

Enter the module name: welcome
Enter the Controller class name: WelcomeController
Enter the Controller method title (leave empty and press enter when done): Welcome Controller
Enter the action method name: welcome
Enter the route path: /welcome
Enter the Controller method title (leave empty and press enter when done): (empty)
Do you want to generate a unit test class (yes/no): no
Do you want to load services from the container (yes/no): no
Do you confirm generation? (yes/no): yes

Drupal Console will then generate the following files for you:


  1. /modules/custom/welcome/src/Controller/WelcomeController.php

  2. /modules/custom/welcome/welcome.routing.yml

And it will rebuild the routes for you. This is similar to rebuilding the menu cache in Drupal 7.

We will dive into the code that was generated in WelcomeController.php and welcome.routing.yml tomorrow,  but for the time being, take a look at the route path that you added:


  1. Enter the route path: /welcome

Remember earlier I said that the route maps the URL to the controller? This means that Drupal Console has added a route for us, mapped it to the controller which handles the output and has created a path of: welcome. Now if you go to /welcome on your site, you should see the following page.

Without writing a line of code, you have just created a custom path and returned some output! 

This tutorial is lesson one in my new Starting Drupal 8 Module Development course. If you would like to get access to all 7 of the lessons, checkout the signup Starting Drupal 8 Module Development page.

May 10 2016
May 10

You have gone through your regular module update routine and cleared the cache. Suddenly you are presented with a PHP Fatal error, because a class can’t be found. Your Drupal site won’t load and you can’t run Drush commands. All you get is the fatal error blocking the site.

An example

On a recent project, a newer version of the Entity Cache module was added and tested.

This is normally pretty trivial, but after clearing cache using Drush, I was presented with the following error:


  1. PHP Fatal error:  require_once(): Failed opening required '/var/www/examplesite/sites/all/modules/entitycache/entitycache.taxonomy.inc' (include_path='.:/usr/share/pear:/usr/share/php') in /var/www/examplesite/includes/bootstrap.inc on line 3161

The Drush command terminated abnormally due to an unrecoverable error.                                                                 

Why can’t Drupal find the class?

The cause was pretty simple - the Entity Cache module had been restructured and files that contain classes had changed location.

Why does moving files cause an error? Why can’t Drupal just load from the new location?

This is because paths to various classes are stored in the registry table in the database. This reduces the number of files Drupal needs to load to process a request and reduces memory usage.

If you get a fatal error like above where a class is not found, a likely cause is that the Drupal registry has the old path for the class. A cache clear should update, but sometimes it all goes a bit wrong.

The solution is to rebuild the registry. This will update the registry table with the new file locations. The [Registry Rebuild] project gives you the tools to do this.

Install and running Registry Rebuild using Drush

Normally you could install registry rebuild and run it using Drush.

Download and enable Registry Rebuild:


  1. drush en registry_rebuild -y

Rebuild the registry:

rr is an alias for the registry rebuild command.

But if the site is blocked because of the fatal error, you will not be able to install the module or run the Drush command. This is because Drupal is loading the class with the old path in its bootstrap, which it needs to run even when you are installing and running registry rebuild. So the very thing that you need to run to fix the problem, you can’t actually run until you fix the problem!

Installing and running Registry Rebuild without Drush or Drupal

Fortunately, there is another handy way to run registry rebuild. This way doesn’t require a full Drupal bootstrap, so will run without the pesky fatal error.

Steps:

  • Download registry rebuild from Registry Rebuild into your modules folder and untar it
  • On the command line, cd to the registry_rebuild folder. In that folder, you will see a file called registry_rebuild.php
  • We want to run that directly using PHP, so run the following command:

  1. php registry_rebuild.php

Once it has finished, the registry table will be rebuilt and will contain the new path for your class. You can now go about your Drupal business without the fatal error. Happy days.

Mar 19 2016
Mar 19

Searching on fields in Views is made possible by adding an exposed filter for the relevant field. You can search on multiple fields by adding multiple exposed filters. But what if you want to have one single search box searching multiple fields? This is a very common requirement.

Fortunately, you don’t need to download any additional modules, or do any view alter magic in code. All you need is the power of combined filters. I’ll illustrate this with an example.

List of users

In this example, the View has a list of users who have registered with the site and you want to allow people to search for users by their name. There are two fields for a user's name: first name and last name.

Drupal view of users without a combined filter

The basic View

This is how the basic view is setup. The View displays the username, first name and second name in a table. The only filter at this stage is the User: Active filter, which ensures that you only see users who are currently active.

Basic user view

Add the fields

Add a new filter. The filter you are looking for is called Global: Combine fields filter. You can either search for it in the Add filter criteria modal, or change the filter to global.

Adding combined filter to the view

Select the first name and second name fields and select the exposed filter checkbox.

Configure the combined filter

I’ve also changed the operator to contains any word.

Head over to the view page, and you will now see the combined filter above the results.

Drupal view of users with the single search box

You can now search for either first name or second name in the single search box.

This is just a simple example. You can combine search filters for other kinds of views e.g. search for nodes using two or more fields from a specific content type.

Caveats

In order to use this, the view format needs to be set to show fields. It doesn’t work if you want to display rendered entities as there are no fields to combine.

The example in this tutorial is for a Drupal 7 site. The option to add combined filters is available in Drupal 8 and it works in the same way. Beware with using the contains any word operator on Drupal 8 as it currently throws an error. There is an open issue about it on [drupal.org]. You can still use the other operators such as contains.

Screencast

If you'd rather see a video walkthrough rather than reading about how to do this, check out the video below.

[embedded content]

Feeling stuck with Drupal 8 module dev?

Get the free 7 lesson course that will help you get started today without feeling overwhelmed.

  • Create Drupal modules with just a few commands using the Drupal Console
  • Create custom pages
  • Create custom blocks
  • Create admin forms
  • Demystify routers and controllers
  • Bonus material

Find out more

Mar 17 2016
Mar 17

You’ll often find yourself having to delete all content for a particular content type. You may have tested imported content via feeds and you then need to delete what you’ve imported. Or content was created that is no longer required. Manually ticking everything in the content list is fine if you have just a handful of nodes, but if you have a lot of content to delete, it can get very time consuming.

There are a few bulk delete modules available, which will do the job. But if you already have devel installed (highly recommended for any sort of Drupal development), you don’t actually need to install anything else. Devel’s Devel Generate module can delete all content for a content type, or multiple content types. Devel Generate is designed to make it easier to create dummy content when building and testing new sites, and then delete that dummy content. But, even if Devel Generate didn’t create the content, it can still delete it.

Drush

The trick is to use the “kill” option . This is what tells devel to delete all content. Normally you’d use kill to delete all dummy nodes that Devel Generate created and then have devel generate a bunch of new dummy nodes. But if all you want to do is delete the nodes and not create any new content (perhaps you want to retest your feed's import), you can specify 0 as the number of new nodes to create.


  1. drush genc 0 --kill

And if you want to delete all nodes from a particular content type, just specify the content type:


  1. drush genc 0 --kill --types=article

And if you want to delete all nodes from multiple content types:


  1. drush genc 0 --kill --types=article, book

UI

You can also do this in the UI if you’d rather not use Drush. Here are the steps.

  1. Go to admin/config/development/generate/content
  2. Select the content type(s) you want to delete
  3. Tick - Delete all content in these content types before generating new content.
  4. How many nodes would you like to generate? - set 0
  5. Click generate

Drupal 7 vs Drupal 8

The process for deleting nodes using Devel generate on Drupal 8 is identical as Drupal 7 for both the Drush and UI method.

Feeling stuck with Drupal 8 module dev?

Get the free 7 lesson course that will help you get started today without feeling overwhelmed.

  • Create Drupal modules with just a few commands using the Drupal Console
  • Create custom pages
  • Create custom blocks
  • Create admin forms
  • Demystify routers and controllers
  • Bonus material

Find out more

Dec 01 2015
Dec 01

You set up a Drupal site for a client and after launch, they complain because important things are not happening. Search is not being updated. Backups aren’t being run. Boost is not being cleared. Logs are not being pruned. All these things should run automatically when cron is run (Drupal comes with the ability to run cron automatically) at specified intervals, such as every 3 hours. So when does cron actually run, if not at the specified interval?

Cron set to run every 3 hours

Setting cron to run every 3 hours does not necessarily mean that cron will actually run every 3 hours. This is because it is not truly automatic. Something needs to trigger cron to run. This trigger happens when a user visits the site. As part of dealing with the page request, Drupal will check for the last time that cron has been run and will run cron as part of the request if it is time to do so. If cron is set to run every three hours and three hours has passed since the last time it has run, it will run as part of the page request for the user that happens to hit the page at that time.

But what happens if you have an extremely low traffic Drupal site, application or intranet? If no one visits the site at all, cron will not run. Or if a user visits the site and cron is run, and another user visits the site 2 hours and 59 minutes later, cron will not run because three hours hasn’t passed yet. If a third visitor visits 3 hours later, then cron will run. But that is almost 6 hours since the previous cron run.

Cron runs

What about sites with enough traffic?

So on low traffic sites, relying on cron to run automatically is problematic because the times that cron runs will be erratic. What about sites that get more traffic? They should get enough traffic so that cron will run more or less on schedule. But they will face another problem - the time it takes cron to run. Because cron is being run as part of a page request for a real user, it can slow down the page for that user.

Solution: Don’t rely on automatic cron runs

The solution is not to rely on the automatic cron runs. Instead, you can set it up on the server as a cron job. This is not reliant on page requests, so will always run on time. And it will run in the background, so will not slow down page requests for users.

Setting up a cron job using cPanel

If you are using a system like cPanel, or Plesk, setting it a cron job is relatively straight forward. Head to the cPanel account for your site and look for Cron jobs under Advanced.

Cron jobs in cPanel

You will be presented with a page where you can add a new cron job. cPanel comes with some preconfigured settings that you can use. For example, you can select Once per day, and the form will get populated automatically.

Running cron every day

You then need to add the Command. This is the URL that will be requested to trigger cron to run. If you look in the root of your Drupal installation, you will see cron.php. The URL hits this file with the addition of a secret key. The secret key is a necessary security precaution to prevent other people from hitting cron.php on your site.

To get the cron URL with your secret key, go to Administration > Configuration > System > Cron. You will see the full URL next to “To run cron from outside the site, go to”

Cron from outside the site

In the Command field in the New Cron job field, add curl --silent followed by the cron URL.

Setting up cron in Linux

If you don't have cPanel, then you can setup cron jobs on the Linux command line. Checkout this great resource for more information: Schedule Tasks on Linux Using Crontab

Disable Drupal’s automatic cron

Now that you have it running on the server level, you can disable Drupal from running the cron. Simply change the Run cron every field to never.

Cron set to never run

Check that cron is actually running.

Wait until cron should run next. Then go back to the cron config page. If it has run successfully, you will see that the Last run has been updated.

Last time cron has run

You can also check the log messages, which will confirm that cron has completed.

Log message showing cron has run

And that is it! Cron will now run in the background according to the specified timeframes regardless of how much traffic your site gets. And as your site grows and gets more traffic, you don’t need to worry about cron slowing down page requests.

Feeling stuck with Drupal 8 module dev?

Get the free 7 lesson course that will help you get started today without feeling overwhelmed.

  • Create Drupal modules with just a few commands using the Drupal Console
  • Create custom pages
  • Create custom blocks
  • Create admin forms
  • Demystify routers and controllers
  • Bonus material

Find out more

Oct 29 2015
Oct 29

If there is one fear that most developers experience, it is the fear of security vulnerabilities with the code you have written. Bugs are one thing, but security holes that can be used to expose user data or wreck havoc on the database are the cause of many a nightmare. One of the most common forms of attack is SQL Injection. SQL Injection involves injecting malicious commands into a query, usually via some form of user entry.

Fortunately, Drupal provides the tools to protect your website or application against SQL Injection, as long as you follow best practice.

This is best illustrated with an example. This is a simple, harmless query that selects everything from the node table:


  1. $query = db_query("SELECT * FROM node");

Let’s say you want to only select a specific node, by its node ID (nid) and display the title of that node in a Drupal message. The nid is found as a query string in the URL. The URL is example.com/?nid=12

To simulate this, you can create a simple implementation of hook_init() - please note that this insecure code!


  1. function blogs_init() {

  2. if (!empty($_GET['nid'])) {
  3. $nid = $_GET['nid'];

  4. $title = db_query("SELECT title FROM node WHERE nid = $nid")->fetchField();

  5. drupal_set_message($title);

  6. }

  7. }

I have added this function to a custom module called blogs. hook_init() is run at the start of every page request, if you add ?nid=4 to any page on the site, you will see the title for the node with a nid of 4 in a Drupal message.

Show node title

There is a serious security vulnerability in this code.
Take a close look at the following:


  1. $title = db_query("SELECT title FROM node where nid = $nid");

At first glance, this might appear harmless enough, but it is actually very dangerous. A user with malicious intent could add a SQL query to the query string in the URL.

Here is an example where the user deletes the first node from the node table:
example.com/?nid=4;DELETE FROM node WHERE nid = 1;

This will turn the SQL query into:


  1. SELECT title FROM node where nid = 4;

  2. DELETE FROM node WHERE nid = 1;

Let's look at another example, where a user with malicious intent get's the title for the first node (node ID 1) and then deletes the same node.

A normal search without any malicious commands:
Normal query

A malicious search:
Bad query

So the attacker will delete the node with node ID (nid) of 1. Even worse, they could delete all data from any table! Not good at all.

Fortunately it is very easy to protect your site against this sort of attack.

Securing against SQL Injection

If you use Drupal’s database layer correctly, you can prevent SQL Injection attacks. Then underlying database system used by Drupal separates the SQL query from any variable data it contains by using prepared statements and variable data is added to the query securely. Always use Drupal’s functions to access the database to protect yourself against SQL injection.

Here is a secure way of getting a specific node title:


  1. $nid = $_GET['nid']

  2. $title = db_query('SELECT title FROM node WHERE nid = :nid', array(':nid' => $nid))->fetchField();

In the above example, we are passing the $nid variable through a placeholder, ':nid'. Drupal then sanitises it to prevent SQL Injection. The use of placeholders ensures that any user supplied data is separated from the query itself, which avoids SQL injection.

Let's add that to blogs_init() and remove the SQL Injection vulnerability:


  1. function blogs_init() {

  2. if (!empty($_GET['nid'])) {
  3. $nid = $_GET['nid'];

  4. $title = db_query('SELECT title FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchField();
  5. drupal_set_message($title);

  6. }

  7. }

Exclude unpublished nodes

The code snippet above will return titles of unpublished nodes as well as published nodes (thanks larowlan for spotting this). In most cases, you will not want to return unpublished nodes. In order to exclude unpublished nodes, add AND status = 1 to the query. A status of 1 simply means the node is published, and 0 means it is unpublished.


  1. function blogs_init() {

  2. if (!empty($_GET['nid'])) {
  3. $nid = $_GET['nid'];

  4. $title = db_query('SELECT title FROM {node} WHERE nid = :nid AND status = 1', array(':nid' => $nid))->fetchField();
  5. drupal_set_message($title);

  6. }

  7. }

Further reading

This is a fairly simple example of creating secure code that is safe from SQL Injection. For more information, check out the following resources:

Feeling stuck with Drupal 8 module dev?

Get the free 7 lesson course that will help you get started today without feeling overwhelmed.

  • Create Drupal modules with just a few commands using the Drupal Console
  • Create custom pages
  • Create custom blocks
  • Create admin forms
  • Demystify routers and controllers
  • Bonus material

Find out more

Oct 16 2015
Oct 16

Dealing with user accounts is an essential part of Drupal development. You’ll find yourself needing to:

  • Change the password for a user
  • Change the password for the admin account on your local site so you can login as admin
  • Block a user to prevent them from logging in
  • Create new user accounts
  • Add a role to a user account so you can test permissions

You can do all of this in the Drupal admin interface, but it takes time. Fortunately, Drush has a rich set of commands to help you save time while performing these necessarily user related chores. And who doesn’t want to save time?

Jump straight to the cheatsheet, or read on for a more detailed list of commands.

Changing passwords for an existing user

You can change the password for an existing user using the user-password command. The alias is upwd.


  1. drush upwd <username> —password=<new password>

Change to the actual username and to the desired password.

Assign a role

You can assign a role to a user, or set of users, using user- add-role command. The alias is urol.


  1. drush urol rolename username

Example

To add the role editor to username test1:


  1. drush urol editor test1

You can also use the user ID or email address instead of the username. For example, to add editor role to user with uid of 3:


  1. drush urol editor 3

Multiple user accounts

You can add a role to multiple user accounts at the same time. To do that, you need to use either the --name,--mail or --uid option.

For example, to add the editor role to users with ids 3 and 4, use:


  1. drush urol editor --uid=3,4

Remove a role

You can remove a role from a user, or set of users, using the user-remove-role command. The alias is unrol.


  1. drush unrol rolename username

Example

To remove the role editor to username test1:


  1. drush unrol editor test1

You can also use the user ID or email address instead of the username. For example, to add editor role to user with uid of 3:


  1. drush unrol editor 3

Multiple user accounts

You can remove a role from multiple user accounts at the same time. To do that, you need to use either the --name,--mail or --uid option.

For example, to remove the editor role to users with ids 3 and 4, use:


  1. drush unrol editor --uid=3,4

Create users

You can create a new user using the user-create command. The alias is ucrt.


  1. drush ucrt <username>

Example

To create a user with username test1:


  1. drush ucrt test1

Add email and password

You can add an email address and password for the user at the same time:


  1. drush ucrt <username> --mail=<email address> -- password=<password>

Setting passwords

You can set a password for a user using the user-password command. The alias is upwd.


  1. drush upwd <username> --password=<password>

Example

Add letmein as a password to username test1:


  1. $ drush upwd test1 --password=letmein

Use speech marks if the password is multiple words. For example:


  1. drush upwd test1 --password=”this is a really long password”

Display information about a user

You can display information about a user using the user-information command. The alias is uinf.


  1. drush uinf <username>

Example

Display information for user with username test1:


  1. drush uinf test1

You can use the user email address or uid if you prefer. For example, to display information for user with uid 3:


  1. drush uinf 3

Multiple users

You can display information for multiple users. Simply add multiple usernames, uids or email addresses, separated with a space. For example, to display information about users with uids 3 and 4:


  1. drush uinf 3,4

To display information for users with username test1 and test2:


  1. drush uinf test1,test2

You can mix them as well. For example, to display information about user with uid 2 and also user with name of test3:


  1. $ drush uinf 2,test3


Note: there is no space between each user identifier.

Block a user

You can block a user using the user-block command. This will prevent the user from logging in. The alias is ublk


  1. drush ublk <username>

Example

Block the user with username test1:


  1. drush ublk test1

Multiple users

You can block multiple users at the same time. Simply add multiple usernames, uids or email addresses, separated with a space. For example, to block users with uids 3 and 4 and username test1:


  1. drush ublk 3,4,test1

Unblock a user

You can unblock a user using the user-unblock command. The alias is uublk


  1. drush uublk <username>

Example

Unblock the user with username test1:


  1. drush uublk test1

Multiple users

You can unblock multiple users at the same time. Simply add multiple usernames, uids or email addresses, separated with a space. For example, to unblock users with uids 3 and 4 and username test1:


  1. drush unblk 3,4,test1

Checkout the cheatsheet

I’ve created an online cheatsheet for easy access to the Drush user commands mentioned above - check it out here

There is the full Drush cheatsheet available (see the box below).

Sep 16 2015
Sep 16

Drupal 8 is (almost) here and if you are a developer, it is time to start learning Drupal 8 module development. Module development in Drupal 8 represents a major shift from Drupal 7. This is the first tutorial in a series where I’ll be going through the process of learning Drupal 8 development. The module that we create in this tutorial is as simple as it can be and is aimed at helping you get a feel for the module structure.

Before we get started, let’s touch on some of the major differences with Drupal 7 module development. Below is a list of concepts that will be used in this tutorial:

  • Drupal 8 is object oriented and Drupal 7 is primarily procedural. This means that a lot of your code will now be in classes rather than simple functions. This is going to be overwhelming for many, but will certainly make module development more flexible and you will upgrade your skills to modern programming practice in the process
  • Each class will need a namespace. Namespacing ensures that your class does not conflict with another class of the same name. You can think of it like a directory in a file system. You can have two files with the same name in different directories.
  • Drupal uses and extends external libraries
  • Drupal 8 uses .yml files instead .info files. .yml files are also used to define menu items, tabs etc.
  • You don’t need to use hook_menu() to map a URL to a callback function. This is now taken care of by a menu class and a route.
  • Drupal 8 uses plugins which provide swappable pieces of functionality. For more information on plugins - check out Joe Shindelar’s explanation on Drupalize.me
  • Drupal 8 uses PHP annotations. Annotations are included in PHP comments and hold vital information about a class. The Drupal 8 plugin system uses them to describe plugins and they are used by Drupal to discover your plugin.

Note: A lot of the code written in this tutorial can be generated by the Drupal Console using generate:module, generate:controller and generate:plugin:block commands (thanks Jesus Manuel Olivas for the tip), so you don't actually have to code it yourself every time you create a new custom module. But for the purposes of learning, I like to do it do by hand first!

Name your module

As with Drupal 7, the first job is to name the module. We need to create a machine name which will be used throughout the system. Machine names are used in functions and methods in your module. For more information on naming your module, check out [naming and placing your Drupal 8 module] on drupal.org.

I’m going to call this module First Module with a machine name of first_module.

Create the module folder

In Drupal 7, core modules go in the /modules directory and you should put your custom and contrib modules in the sites/all/modules directory or sites/sitename/modules directory. In D8, core modules go into the /core directory so you can put your contrib and custom modules in the /modules directory. I’m going to stick with the Drupal 7 practice and put them in sites/all/modules.

  • Create a new directory in /sites called all
  • Inside all create a directory modules
  • Create the directory called first_module inside the all directory

Create a info yaml file

You need to create an info yaml file to tell Drupal that your module exists. This is similar to creating a .info file in Drupal 7.

The filename should be the machine name of your module with the .info.yml extension. In this case, it will be first_module.info.yml.

  • Create first_module.info.yml in the root of the first_module directory.

  1. name: First Module

  2. description: An experimental module to build our first Drupal 8 module

  3. package: Custom

  4. type: module

  5. version: 1.0

  6. core: 8.x

More information on .yml files.

Create a .module file

In Drupal 7, the .module is required even if it doesn't contain any code. in Drupal 8, it is optional. I'm going to create one which can be used later if you need to implement hooks.

  • Create one called first_module.module in the root of the first_module directory.

Create the src directory

We need to create a subdirectory within the module directory for the controllers, plugins, forms, templates and tests. This subdirectory should be called src, which is short for source. This will allow the controller class to autoload, which means you do not have to explicitly include the class file.

  • Create a folder inside the first_module module folder called src, which is short for source.

Create a basic controller

Controllers do most of the work in a typical MVC application. Here are the steps to create the basic controller for the module:

  • Create a folder within src called Controller.
  • Within the Controller folder, create a file called FirstController.php.

In FirstController.php, we will create a simple “hello world” message so that we know it is working.


  1. /**

  2. @file

  3. Contains \Drupal\first_module\Controller\FirstController.

  4.  */

  5. namespace Drupal\first_module\Controller;

  6. use Drupal\Core\Controller\ControllerBase;

  7. class FirstController extends ControllerBase {

  8. public function content() {

  9. '#type' => 'markup',

  10. '#markup' => t('Hello world'),

  11. );

  12. }

  13. }

Add a route file

The controller we created above will not do anything at this stage. We need to wire it up to a route from the URL to the controller in order for it to be executed.

  • Create a file called first_module.routing.yml
  • Add the following code to first_module.routing.yml

  1. first_module.content:

  2. path: '/first'

  3. defaults:

  4. _controller: 'Drupal\first_module\Controller\FirstController::content'

  5. _title: 'Hello world'

  6. requirements:

  7. _permission: 'access content'


View the content

If you now go to /first, you will see the Hello World message that is being returned from the controller.

Create menu link

The route now works and returns content from the controller. But you’d need to know the URL in order to reach the content. To make this more useful, we need to add it to Drupal’s menu system. To do that, you need to create a menu .yml file.

  • In your module root, create first_module.links.menu.yml
  • Add the following:

  1. first_module.admin:

  2. title: 'First module settings'

  3. description: 'A basic module to return hello world'

  4. parent: system.admin_config_development

  5. route_name: first_module.content

  6. weight: 100

  • Clear the cache and then you should see the menu item under configuration -> development.
  • Click on the menu item and it will take you to /first.

And that is it, your first Drupal 8 module with a menu item that returns something!

A custom block

So far, we have a custom path and menu item which displays a title and string. Let’s do something a bit more exciting and add a custom block to the module.

First, we need to create a new plugin for the module. [Plugins] are new in Drupal 8 and provide swappable pieces of functionality.

  • To get started, create a new directory in the module’s src folder called Plugin. This is where you can store all of your plugins for this module.
  • Then create a folder called Block. A block is a plugin type.
  • And inside the Block folder, create a file called HelloBlock.php.

In that file, we need to define the namespace and class. If you use an IDE like PHPStorm, this will be created automatically.


  1. namespace Drupal\first_module\Plugin\Block;

  2. class HelloBlock {

  3. }

You need to extend the BlockCase class. To do that, add the following use statement:

use Drupal\Core\Block\BlockBase;

And then change class HelloBlock to HelloBlock extends BlockBase


  1. namespace Drupal\first_module\Plugin\Block;

  2. use Drupal\Core\Block\BlockBase;

  3. class HelloBlock extends BlockBase {

  4. }

Another new concept to Drupal that we need to use for the block is Annotations. In order for Drupal to find your block code, you need to implement a code comment in a specific way, called an Annotation.

  • Add the following comment, above class HelloBlock extends BlockBase :

  1. /**

  2.  * Provides a 'Hello' Block

  3.  *

  4.  * @Block(

  5.  * id = "hello_block",

  6.  * admin_label = @Translation("Hello block"),

  7.  * )

  8.  */

Next, we need to inherit docs from the base class and add a build method, which will return the content of the block.


  1. class HelloBlock extends BlockBase {

  2. /**

  3.   * {@inheritdoc}

  4.   */

  5. public function build() {

  6. '#markup' => $this->t('Hello, World!'),

  7. );

  8. }

  9. }

Now that you have defined the block, head over to the blocks page (/admin/structure/block). On the right hand side, you will see the block listed under the heading First Module. Add it to a region like Sidebar first. And now you should see the block in the left side bar!

The full code for HelloBlock.php


  1. <?php

  2. /**

  3.  * @file

  4.  * Contains \Drupal\first_module\Plugin\Block\HelloBlock.

  5.  */

  6. namespace Drupal\first_module\Plugin\Block;

  7. use Drupal\Core\Block\BlockBase;

  8. /**

  9.  * Provides a 'Hello' Block

  10.  *

  11.  * @Block(

  12.  * id = "hello_block",

  13.  * admin_label = @Translation("Hello block"),

  14.  * )

  15.  */

  16. class HelloBlock extends BlockBase {

  17. /**

  18.   * {@inheritdoc}

  19.   */

  20. public function build() {

  21. '#markup' => $this->t('Hello, World!'),

  22. );

  23. }

  24. }

Place block

Add add the block to a region, head on over to the blocks admin page. To add the block to the sidebar first region, click on the Place block.

Place a block button

A modal will appear with a list of available blocks. Search for hello to find the Hello block you just created. Click the Place block button in the operations column.

Hello block in the block listings

You will then get a configure block form. Save that.

Configure block form

Then scroll to the bottom of the blocks layout page and click save blocks. You should now see the block in the sidebar left of your site.

Hello block in the sidebar

Overview of the file structure

Once you have completed the above steps, you should have a file structure that looks like this:

Wrapping up

In this tutorial, you have created a basic Drupal 8 module, mapped a URL to a controller which returns a string and created a custom block. Don’t worry if the concepts are totally alien at this stage. Just keep practicing and over time, everything will become clear. And remember, you are not alone - the transition to Drupal 8 is a journey the entire Drupal community is embarking on.

Update - 4 Dec 2015

Fixed indentation with first_module.routing.yml

Update - 12 Dec 2015

Moved the comment annotation to be directly above the HelloBlock class.
Added steps on placing the block in a region.

Jun 24 2015
Jun 24

Site speed is one of the most critical factors when running a site. If a site takes too long to load, visitors will hit the back button and go elsewhere. Google is well aware of this, so slower sites will not rank as well as fast sites (all other things being equal).

Drupal performance optimisation can be a complicated specialisation in its own right. Consultants and Drupal agencies can spend days or even weeks investigating performance issues and fixing them. But there are many quick fixes and simple methods that you can implement right away. You don’t have to implement absolutely everything on this list - you can implement some and monitor the difference it makes to the site speed.

There are two great tools for monitoring the changes you make - YSlow and GTmetrix. Make a record of the key metrics (page load time, page size, number of HTTP requests and the overall speed grade) before you make each change and then afterwards, noting the difference to ensure you are moving in the right direction. You can also try tools like JMeter and Apache Bench.

Core caching and aggregation

Turn on core page caching

It might sound obvious to enable the default caching, but a lot of people miss this. This caches the entire rendered page in the cache table. So when each page is served, it is served from just the single cache table without all of the numerous queries being run. This is a significant performance boost. This only works for anonymous (non-logged in) users because pages for logged in users is dynamic and can be different for each user, so caching the entire page is a lot more difficult.

You can enable page caching in the core performance settings page, which can be found under Configuration -> Performance (admin/config/development/performance).

Turn on core block caching

While page caching caches entire pages, block caching caches individual blocks. This is useful for two reasons: 1) blocks will be cached if you have page caching turned off and 2) even if you have page caching on, it will not work for authenticated (logged in) users, so block caching will represent a performance boost for them.

You can enable page caching in the core performance settings page, which can be found under Configuration -> Performance (admin/config/development/performance).

Enable built in CSS and JS aggregation

In the same admin form as the default caching, you can enable CSS and JS aggregation. If aggregation is left off, there will be many CSS and Javascript files. Each one represents a separate HTTP request and having that many requests can slow page loads down. Enabling aggregation converts all of the many CSS and Javascript files into just a few files, reducing the HTTP requests by a lot.

You can enable page caching in the core performance settings page, which can be found under Configuration -> Performance (admin/config/development/performance).

Views caching

Enable Views caching

Views is the most popular Drupal contributed module (and will be in core in Drupal 8) and is used on most sites.

Without caching, every time a user requests a page with a View on it, a database query is done. These database queries can add a lot of time to page loads. If you enable Views cache, the result is stored in the Views cache table and that is used for the duration of the cache, rather than the query being run every time.

Views caching is great for logged in users (authenticated) because the output of the View is still cached even if the rest of the page is not.

Enable Views Content Cache

The downside of Views caching is that results are not real time. If you set the cache time for 10 min, the results will be the same for the next 10 min. If the content returned from a View changes, a new cached version of that View is not triggered. You simply have to wait for the full 10 minutes.

Views Content Cache fixes this problem. It will monitor the content that the View is returning and clear the cache if that content changes.

To use it, after enabling the module, go to basic settings for the View. Under Page: Caching options, select the events that you’d like to use to update the View.

Enable Views block cache

Drupal has built-in block caching. Blocks that are generated from Views are excluded by default, but can be included with Views Block Caching.

For a given View, head to the advanced settings. At the bottom, you should see Block caching. You will choose to either cache the views block once or more granularly such as per page, per role, per user.

Enable Views Litepager

If you have the default pager or Mini Pager enabled on views, COUNT queries need to be executed for first, page number and last. This adds extra processing time and is especially expensive when using MySQL’s InnoDB engine (InnoDB is recommended for high traffic sites).

Views Litepager removes the need for these COUNT queries by displaying only the previous and next links. You lose some functionality, but you gain in page speeds.

Flat HTML files

Enable Boost

Boost turns all Drupal pages into flat HTML files and stores them in a cache folder. When a user visit the page, the server returns the cached file from disk and there is no PHP and MySQL processing. This makes a huge difference to page speeds. Boost is especially good for smaller sites on shared hosting where Varnish (Varnish caches pages to memory instead of the file system and is even faster) is not an option. Boost works for anonymous users, but users that are logged in (authenticated) will still get normal Drupal pages.

Faster 404 errors

Enable Fast 404

Page speeds will be significantly slower if your site has broken images or CSS file paths. Drupal will still attempt to load these paths for every page in which they are included but they will return a 404 error (page not found). Drupal will do a full bootstrap when serving a 404, so this consumes a lot of resources.

The Fast 404 module changes the way 404 errors are handled, delivering fast 404 error messages.

Disable bad core modules

Uninstall the Statistics module

The Statistics module collects similar data to Google Analytics such as how many times content is viewed. This adds multiple database writes per page load.

It is recommended that you use Google Analytics (or a similar analytics system) instead to save these database writes. You can use the Google Analytics Reports module to such the Google Analytics data in so you can still view it within the Drupal interface.

Disable PHP Filter

The PHP Filter module allows you to use a PHP text format. This means that you can store PHP in the database and run it on page load. This is both a security problem and a performance problem and should never be done. All code should be in modules, not in the database.

To remove it, simply disable the PHP Filter module. Don’t delete the module itself though, because it is a core module.

Disable Update Manager

The Update Manager polls Drupal.org to check for modules that are out of date and ready for an update to be applied. While it is essential that you know which modules need updating, it does add load to the server to check.

If you have a staging or dev site, you could run update manager there instead of your live site.

Reducing the number of modules

Limit the number of modules in use

Every module you have installed and enabled will add to the page execution time. So don’t just install every module you think you might need - really think about what modules you actually need.

In many cases, it is better to create a custom module to do exactly what you need than it is to install a large module and only use 10% of its functionality. This is especially true if you find you're having to use multiple modules together to get your desired functionality.

Disable and uninstall unused modules

You (or your developers) might remove modules without disabling them first. This means that they are still enabled in the database even though they are missing from the file system. This can have a huge negative impact on performance.

You can find these modules using Clean Missing Modules or Missing Module and then disable them.

Images

Resize images

Images with large file sizes take a long time to load. Wherever possible, you should resize images so they are not larger than they need to be.

For image fields, you can use image styles (admin/config/media/image-styles) to resize images to the desired size.

The Image Resize Filter makes this a lot easier for inline images that are inserted via a WYSIWYG editor like tinyMCE or CKeditor. If you set the height and width of an image, Image Resize Filter will automatically resize it to match.

Install ImageAPI Optimise

Even when using image styles (ImageCache in Drupal 6) and Image Resize Filter to scale images down to the correct size, images still contain data that slow down load times. The ImageAPI Optimise module removes this data without affecting the quality of the image.

The ImageAPI Optimise module does require certain utilities to be installed on your server. If you don’t have access to your server, you can use Yahoo’s Smush.it, which you can select in the settings.

Advanced caching

Install Entity Cache

Content, users, taxonomy terms are all Entities in Drupal 7. Most entities have fields and when each entity is loaded, queries are performed to load the fields to. The end result is a lot of queries!

Entity Cache helps address this by caching the fully loaded entities into a cache table. Each loaded entity is a row in the cache table.

Using this module is as simple as it comes. Just enable it. No configuration required.

A word of caution. I have seen significant performance improvements on complex sites with large sets of fields, Field Collections etc. Your mileage might not be as great if you have a simple site. Also, I have found that there is an incompatibility between Ubercart Price Per Role and Entity Cache.

Install Advanced CSS/JS Aggregation

While built in core CSS and Javascript aggregation does a decent enough job at reducing the number of requests that the browser needs to do by aggregating it into one file, the Advanced CSS/JS Aggregation module does this in a much more intelligent way.

Limit the number of times cron is run

Drupal 7 has a feature that allows cron to be run automatically without having to set it up on the server. The downside is that it is run when a real user hits a page. By default, it will run every 3 hours. This means that the first user to hit a page after that 3 hours will trigger cron, making the page very slow for them.

The best solution in terms of performance is to disable this and set cron on the server so that it is run in the background.

Wrapping up

This represents a run down of some of the easier solutions to boost the performance of Drupal sites. As mentioned at the top of the post, monitor the changes to be sure there is actually an improvement (there is no point in using something if it doesn’t actually help).

Let me know in the comments below if you can think of any more quick wins to improve performance of a Drupal site.

Need help?

If you would like an experienced Drupal developer to improve the performance of your site, get in touch.

Jan 06 2015
Jan 06

Simple on/off checkbox fields where you want the user to select or deselect a checkbox are a common requirement. Yet it is not so obvious how to create them in Drupal.

On/off checkbox fields are often used to toggle for things like:

  • Accepting terms and conditions
  • Accepting privacy notices
  • Marking an event as cancelled
  • Flagging a node or user

To create a check box, you first need to add a field with field type of Boolean. Set the widget to Single on/off checkbox.

Boolean field with Single On/Off widget

You will then be presented with the field settings. You don't need to change anything here.

Single on/off field settings

On the next screen, select the option to Use field label instead of the "On value" as label. This will ensure that the label for the field is displayed with the field rather than the number 1.

Use field label

And that is it! You now have a nice on/off checkbox.

Create check box field

UPDATED 6 Jan: Thanks to everyone who pointed out the boolean type and single on/off widget this out in the comments!

Feeling stuck with Drupal 8 module dev?

Get the free 7 lesson course that will help you get started today without feeling overwhelmed.

  • Create Drupal modules with just a few commands using the Drupal Console
  • Create custom pages
  • Create custom blocks
  • Create admin forms
  • Demystify routers and controllers
  • Bonus material

Find out more

Dec 18 2014
Dec 18

As the Drupal market continues to rock and roll, more and more clients need "Drupal Developers". But what exactly is a Drupal Developer? A Drupal Developer is someone who knows Drupal right? Right?!

There always has been some confusion around job titles and skills in the Drupal world. This is especially true with some recruiters and even managers and clients that are hiring. In effect, there are three main areas of expertise in the Drupal world: site building, backend/module development and theming. The skills required for each are quite different.

Drupal Site Builder

A Drupal site builder is someone who builds Drupal sites with point and click in the admin UI without writing much or any custom code. I say much, because they might implement the odd hook here and there. But most of the functionality of the site/application comes from configuring Drupal core and contributed modules. Site Builders will have experience with a wide range of contributed modules and will understand how they work together to solve a particular problem. They will understand the limitations of modules and should be able to provide a concise argument of the pros and cons of various solutions. Site Builders will build the content types, taxonomy, navigation, image presets, rules etc. One of the magnificent things about Drupal is that it does not exclude non-developers. The Drupal community and the platform provides a very powerful tool set for people to build innovative and complex sites without the requirement to be a programmer. And mastering this is a very valuable skill in itself.

Drupal Themer / Frontend developer

A Drupal Themer is the specialist front end developer. They are experts in HTML, CSS and Javascript. They are also experts in the Drupal theme layer. They should be able to take a design and turn it into the working theme. Ideally they will be well versed in implementing responsive design.

Drupal Module Developer / Backend developer

A Drupal developer is someone who writes a lot of PHP and other server side languages. They write custom modules, automated tests, consume web services, automate deployment etc. They may also be know as “backend Drupal developers”. They may also get involved in some of the more advanced side of the theme layer as well. Often they will set up automated deployment.

A note on contributing and collaboration

Drupal is inherently a collaborative project. Site Builders, Module Developers and Themers will often contribute their work back to the community and collaborate with others. It is common for module developers to share and collaborate on contributed modules, themers on contributed themes and site builders on site building recipes and other forms of documentation.

The Three Disciplines

What is a drupal developer?

Drupal Generalist /Jack of All trades

It is very common to do all three. You may be more advanced in one area or another, but still act in a general capacity.

In most of the projects I have worked on, there has not been a dedicated site builder. Both backend developers and fronted developers will do elements of site building.

A Drupal developer who is a jack of all trades

How a business hire and use Drupal people varies. In one extreme, a business may hire specialists. To deliver a particular piece of functionality or feature, the work may have to go through each of the specialities before it is done. One of the other extremes, is a business may assume that a Drupal person should do all three specialities. In this case, it is common for the team to be a team of “Drupal Developers” and the role encompasses site building, backend development and theming. Both approaches have their pros and cons, but that is for another day!

Other roles

Just like any other web development setup, there is a range of other roles included in the process of building and support Drupal applications. This includes:

Sysadmin / Devops - run the live stack, will often deploy Drupal sites to the live environment, deal with performance issues, setup a CDN, Varnish, Memcache etc.

QA - test all changes to ensure quality and that they meet the requirements. Setup automated tests.

Project Manager / Scrum Master - run the scrum team, remove impediments to progress, ensure delivery of the project on time and budget.

Product owner - comes up with the requirements. Works closely with the project manager to prioritise the backlog. Normally has final sign off of all changes.

Design / UX - comes up with the design and user experience. They might build prototypes that can then be converted into a Drupal theme.

Who is not a Drupal developer

This advice should be given to any recruiter or hirer in the Drupal space. A PHP developer is not necessarily a Drupal developer. It does not matter how good the PHP developer is at PHP, unless they have decent Drupal experience and understand its API’s and, dare I say it, “the Drupal way”, they will make many mistakes. You will then need to hire an experienced Drupal developer to clean up the mess left behind. Of course, I am being a bit simplistic here. In current times where demand far out strips supply, it as not as simple as that. You might be forced to hire a PHP developer who doesn’t have much (or any) Drupal experience. That is fine, but the minimum requirement should be that the developer wants to learn the Drupal standards, understand the Drupal culture and try and do it the Drupal way. You don’t want someone who treats it as just another framework that can be bent and broken to the developer's way of thinking. That is a recipe for creating a maintenance nightmare that will cost the business in the long run.

Where do you go from here?

When I first launched the early edition of my book, Master Drupal Module Development, I asked people on my mailing list whether they were Site Builders, Backend Developers or Themers. The split was mostly between Site Builders and Backend Developers but most of the developers were people who developed in tech outside of Drupal and were looking to find the best way to learn Drupal development.

Developer from another technology

If you are a developer from another technology, there are a couple of routes available. One is to build a couple of sites using pure site building. And then, once you are happy with the basics of Drupal, you could dive into module development. Being an experienced developer will almost certainly give you a leg up, because you obviously already know how to do development. Now you need to learn the Drupal APIs and the Drupal way.

Site Builder

If you are a site builder, you might want to move into module/backend development. Because you already know your way around Drupal, you also have a leg up. However, you still need to learn the Drupal APIs and the Drupal way of developing. In addition, if you don't have any or much programming experience, you will need to learn some of that to. You don't need to be a programming genius to get started with Drupal development. You can get started with just the basics of PHP in addition to something like my book.

Themer

If you are a themer, you might not want to do anything else but get better at theming and frontend development. After all, there is a ton to master there, especially when you include CSS and Javascript. But you might want to learn some module development. It will certainly help you broaden your horizons and achieve more.

Module Developer

If you are a Module/Backend Developer, then where do you go? You can always learn more about theming. But might also consider learning more about being a solutions architect. If you are happy being a backend developer, then getting more experience in other PHP frameworks (such as Laravel or Symfony2) is very beneficial to your career prospects and capabilities as a developer.

Where do you fit in?

Do you consider yourself a Site Builder, Themer, Module Developer or something else? Are you looking to move into another area? If so, what are your main obstacles? I'd love to hear from you in the comments below.

Dec 12 2014
Dec 12

This post is part of a series of posts on making changes in Drupal programmatically rather than in the Drupal interface.

The problem

You want to programmatically assign role(s) to user(s). You would typically do this in a site deployment module in order to automate this task rather than having to manually assign the roles in the Drupal UI.

One role to one user

If you only have to assign a single role to a single user, the code is relatively simple.

In this example, we will assign the 'editor' role to a user with a user id of 2.


  1. $uid = 2;

  2. $role = user_role_load_by_name("editor");

  3. user_multiple_role_edit(array($user->uid), 'add_role', $role->rid);

First we load the role by its name and then use user_multiple_role_edit() to save it to the user. user_multiple_role_edit() is a callback function that is used when mass updating user accounts. It comes in very handy for cases like this.

Alternative method

Another way to do this is to add the new role to existing roles for the user and then save the user object.


  1. $uid = 2;

  2. $user = user_load($id);

  3. $role = user_role_load_by_name("editor");

  4. $user->roles = $user->roles + array($role->rid => $role->name);
  5. user_save($user);

But this is an extra two lines of code. First we need to load the user by its ID and then the role by its name. Then join (using array union) the new role to the existing roles ($user->roles) and assign it to the user role's property. Finally, save the user object. This will work, but using user_multiple_role_edit() is simpler and cleaner.

One role to more than one user

In this example, we will assign the editor role to three users. The three users have user IDs of 2, 7 and 61.


  1. $users = array(2, 7, 61);
  2. $role = user_role_load_by_name("editor");

  3. foreach($users as $uid) {

  4. user_multiple_role_edit(array($user->uid), 'add_role', $role->rid);
  5. }

This is similar to the first example, except here we are looping through the array of user IDs and then calling user_multiple_role_edit() for each one in turn.

More than one role to more than one user

This is the most complex of our examples because we need to assign more than one role to more than one user.


  1. $users = array(2, 7, 61);
  2. $roles = array("editor", "administrator");
  3. foreach($users as $uid) {

  4. foreach($roles as $role) {

  5. $role = user_role_load_by_name($role);

  6. user_multiple_role_edit(array($uid), 'add_role', $role->rid);
  7. }

  8. }

In this example, there are two roles in an array, editor and administrator. We loop through each user and then loop through each role. Then we assign the individual roles to each user.

Wrapping up

This is three examples of programmatically assigning roles to users. You would normally do this in the Drupal UI but it is sometimes helpful to do it in code in order to automate it during deployment and save any manual steps. For more information on making changes programmatically during deployment, check out my post on creating a site deployment module.

Feeling stuck with Drupal 8 module dev?

Get the free 7 lesson course that will help you get started today without feeling overwhelmed.

  • Create Drupal modules with just a few commands using the Drupal Console
  • Create custom pages
  • Create custom blocks
  • Create admin forms
  • Demystify routers and controllers
  • Bonus material

Find out more

Nov 27 2014
Nov 27

Keeping your Drupal site up to date has always been of critical importance to ensure it remains secure. Last month's announcement of a SQL Injection vulnerability and subsequent announcement of automated attacks within 7 hours caused wide spread panic across much of the Drupal community.

Here are eleven tips to ensure your modules and core code are up to date with the latest security releases.

1. Follow security news

If you are not already following Drupal security news, it is time to start now. This is how you get alerted to security updates as soon as they are announced.

You can get security advisories from these places:

2. Check update report regularly

The update report (available here: admin/reports/status) will alert you to problems with your Drupal site, including security issues such as out of date modules, Drupal core or database updates that need to be run.

You can get notified when updates are available by adding your email address here: /admin/reports/updates/settings

Pro tip: for performance reasons, it is better to have the Update Manager module enabled and running on a dev or staging site than the production site.

3. Apply security patches asap

When a security update is announced, apply it as soon as it is humanly possible. In the past, we had a day or two to apply updates and still be safe. With larger companies, deploying an update needs to fit into a regular deployment timeline, so it could take many days or even weeks. That didn't used to be much of a problem. It is now. The first known attack following the Oct 15th security advisory happened within 7 hours. This meant that you only had 7 hours to update and be safe from potential attack.

So be ready every Wednesday. I've put a recurring task in my todo manager for every Wednesday.

4. Use Drush to update.

You can download Drupal core and modules from drupal.org and manually apply them to your Drupal codebase. This gets tedious very fast. To make this a painless experience, you can use a couple of Drush commands instead.

Check what has changed

pm-update --pipe (alias: up --pipe) - lists projects that need to updated. You can then go and check the release notes to see what has changed. [Thanks to James Oakley for this tip]

Run the updates in one step

drush pm-update (alias: up) - update Drupal core, modules and themes and run any pending database updates

Run the updates in two steps

If you'd rather not run pending database updates at the same time as updating the code, you can run these two commands instead:.
* drush pm-updatecode (alias: upc) - Update the code
* drush updatedb (alias: updb) Run the pending database updates

You should not run these commands directly on the production site. Instead, run them on your local version (or another dev version) and ensure nothing breaks before applying to the production site.

5. Don't hack contributed modules or core code.

It maybe quicker to make changes to contributed modules or Drupal core, but this leads to a long term nightmare for keeping your core base up to date. If they are left untouched, upgrading is a painless experience. If they have been altered, you will lose any changes when you upgrade that you will need to re-apply.

6. Check if your contributed modules or core code have already been hacked

If you didn't develop the site yourself, you may not know if someone else has hacked contributed modules or Drupal core. Fortunately, you can easily check by using the Hacked module.

Hacked is a great utility module which will check if your contributed and core modules have any differences to what is stored on drupal.org. Checking this will see if anyone has meddled with your code.

7. Create patches for hacked contributed modules or core code

If, after running the Hacked module, you discover that someone has altered contributed modules or core code, then it is best to store these changes in a patch file. Then when you need to update your Drupal install, you can re-apply the patch file. This will save a lot of time as opposed to manually re-applying the changes.

8. Backup your database automatically

It goes without saying that you should be running database backups automatically on a regular basis. For more information, check out my recent article on backing up to Amazon S3.

9. Check your backups are working

Automatic database backups are great, but what if they are not working? It is a good idea to periodically restore them and make sure everything is in order.

10. Have code in a version control system

Storing code in a version control system (such as Git) is great for a variety of reasons. When you apply security updates, you can see exactly what has changed if your code is stored in source control. This makes it much easier to ascertain the potential risk of the update breaking your site.

It also provides you with a method of checking if your site has been hacked because you can check for differences with the code stored in version control.

11. For clients: Pay for a quality security and maintenance contract

If you don't have time or knowhow to keep it up to date and secure, seriously consider paying for a maintenance contract from an experienced Drupal consultant, or agency.

Feeling stuck with Drupal 8 module dev?

Get the free 7 lesson course that will help you get started today without feeling overwhelmed.

  • Create Drupal modules with just a few commands using the Drupal Console
  • Create custom pages
  • Create custom blocks
  • Create admin forms
  • Demystify routers and controllers
  • Bonus material

Find out more

Nov 05 2014
Nov 05

There is a lot of talk in the Drupal community and media about the Drupal security vulnerability that was fixed in the Oct 15th update (7.32). If you have missed the details, here is a summary:

A highly critical security vulnerability was found in Drupal's database abstraction API. This was fixed with the release of 7.32 on Oct 15th. If you did not upgrade to Drupal 7.32, or apply the patch, by 11pm UTC on Oct 15th, your site may be compromised. Applying the patch after this will not remove any backdoors that may have already been added to your site or server.

So, if you upgraded or applied the patch immediately, or within 7 hours, you are safe (or as safe as you can be). But if you didn't, it is time to take precautionary measures. The best and most effective measure to take is to roll back everything to a state before Oct 15th 2014. And when I say everything, that includes the server not just the web files and database because backdoors could have been added anywhere.

I upgraded my clients site within 1 hour of the 7.32 release, so they are safe. But I had a couple of personal sites where I missed the 7 hour window by a few hours. Despite not seeing evidence of any hacking, I performed the rollback as a precautionary measure. What follows are my notes on how I did this.

This is what I had:

  • 3 sites running as a multisite
  • Shared hosting with WHM and cPanel
  • Database backups to Amazon S3
  • Code version controlled using Git

Prepared the clean site

I prepared the clean site on my local machine. I got the database backup that pre-dated Oct 15th from Amazon S3 and restored to the local version of the site.

Import new content

I imported any new content created on the live site after Oct 15th. If the site isn't updated that frequently, you may do this manually. Or you could export content using Node Export module. Just be sure to inspect every node for possible anything that may have been added by a hacker.

In my case, I manually recreated the new nodes. I did this by going to admin/content on both the local site (which now has the clean database from the backup) and the live site and created anything new. For each node, I changed the Authored on date to reflect the original publication date. I also made sure that the node IDs matched both the live site and the local site by creating the nodes in the correct order. This is essentially for importing comments.

Import comments

The first thing I did was moderate any new comments on the live site. Then I checked the last cid in the comment table on the local database. Following that I went to phpMyAdmin for the live site and selected all rows after that cid and exported those rows, and after that imported into my local site. There are three tables that contain comment data that need to be exported/imported: comment, field_data_comment_body, field_revision_comment_body, node_comment_statistics.

Copy files

Copy down any new images in the files directory. Be really careful here because the files directory is a common place to put dodgy files. Make sure you only download images (PNG, JPEG, GIF). Do NOT download the .htaccess file (this could have been hacked).

Backup live database and files

Backup the current live files and database. I did this to a new Amazon S3 bucket. I'm only doing this just in case I need it again in the future.

Dump local clean database

Dump the local, clean, database. This now has the new nodes and comments. I prepended the database name with "clean_" to make it clear that this is the clean database that I'll use to import on the live site.

Rebuild the server

Now it is time to wipe the server and rebuild it. In my case, this is managed by WHM. So I simply terminated the cPanel account and recreated it again. This gives me a fresh clean cPanel account to add everything to again. I spoke to the host before doing this and they agreed that it was the best course of action.

Please bear in mind that this will remove your databases, database accounts and mailboxes. My email runs through Google Apps, so that wasn't a problem.

DNS issues

There was a DNS issue when I recreated the cPanel account. A quick email to the host and they sorted that out.

Deploy the code

With the old database and files gone, it is time to deploy the files, create the databases, users and import the database.

Drupalgeddon free

Now I am safe in the knowledge that the site and server is in a state pre-dating Oct 15th with content migrated and safe from Drupalgeddon.

What if you don't have backups?

In order to roll back like I outlined above, you need database backups and version controlled code. If you don't have both of those things, it gets a whole lot messier. You have two options:

1) Throw everything away and start again

2) Check for common signs of vulnerabilities. And then keep checking on a regular basis. Here are some resources to help you with that:

Going forward

Drupalgeddon serves as a reminder to everyone to have a proper backup strategy, store your code in version control and be ready to apply security updates when they are released every Wednesday.

You can get security advisories from these places:

Feeling stuck with Drupal 8 module dev?

Get the free 7 lesson course that will help you get started today without feeling overwhelmed.

  • Create Drupal modules with just a few commands using the Drupal Console
  • Create custom pages
  • Create custom blocks
  • Create admin forms
  • Demystify routers and controllers
  • Bonus material

Find out more

Oct 09 2014
Oct 09

We all know the importance of backing up the database for each Drupal site we build and maintain. But it is not uncommon for this to be put on the back burner and never actually implemented. Fortunately, it is really easy to setup with a combination of Amazon S3 and Backup and Migrate.

Why Amazon S3?

Some people backup to the same server that the database is on using a cron job. While backing up to the same server is better than not backing up at all, it is better to backup to an external location. Amazon S3 is a popular choice for this.

Create Amazon S3 account

If you don't have an Amazon S3 account, you can create one here: http://aws.amazon.com. Amazon offer a lot as part of AWS and you will be presented with all of them. All you need is S3.

Find the S3 option in the AWS Management Console

You will need to create a bucket for your database backups. Bucket names must be unique, so no other account can have the same bucket name. You might want to prepend your site name to the bucket name to ensure it is globally unique. You will find more information on about buckets in the AWS documentation.

When you create the bucket, you will need to select the region. It is generally a good idea to chose one that is in the same region as you, or as close as possible. This will reduce latency and also (probably more importantly for backups) ensure you comply with local regulations. For example, if you are inside the EU and your database contains user data, that data should be stored within the EU (or a safe harbour if outside the EU). Therefore, EU based residents or businesses might want to chose Ireland, which is Amazon's location within the EU.

Select a region

Now that you have your Amazon S3 account and bucket, you can integrate it with Drupal.

Setup Backup and Migrate

The easiest way to do this is using the wonderful Backup and Migrate module.

Here is how:

1. Install Backup and Migrate

Install the Backup and Migrate module

2. Backup and Migrate - Destinations tab

Go to the Backup and Migrate settings page. You will find it at Configuration > System > Backup and Migrate.
Go to the Destinations tab and click on Add Destination and then Amazon S3 Bucket.

Fill in the Backup and Migrate S3 settings

You are going to need the following from your Amazon Webservices account:

  • Host: s3.amazonaws.com
  • S3 Bucket: The bucket name you created above
  • Access Key ID: Go to Security Credentials (click on your name in the top navigation), expand Access Keys (Access Key ID and Secret Access Key) and create new access key.

Find the Security Credentials link

Expand the Access Key group

3. Create a Schedule

You can create a schedule, which is the frequency the backup will run. You can create a new schedule from the Schedules tab in the Backup and Migrate settings. Decide on a frequency that makes sense for your site. If you publish content daily, or multiple times per day, you might choose to backup daily. If you publish weekly, then a weekly backup is probably fine.

You can also set the number of backups to keep. Leave this as 0 if you want to keep all of them. But remember, you are paying Amazon to store these backups, so you might want to set a limit.

In order for backups to run, you need to have cron running. The easiest way to do this is to head over to Configuration > System > Cron. Set the frequency under Run cron every.

Drupal cron settings

Cron is used to run a variety of tasks and the frequency that you run it will depend on your specific needs. For my site, I run cron once a day. It is not uncommon for cron to be run hourly for sites with more frequently added content.

With the cron frequency set, it will get triggered to run automatically when people visit the site. This can slow the site down, and you may elect to run cron overnight instead. This is outside the scope of this tutorial, so head over to the Drupal handbook for more information.

The backup source will normally be the Default Database, and the Settings Profile the Default Settings.

Restoring

Restoring a database is a one click job. Beware though, you will lose any content that has not yet been included in a backup. To see a list of all of the database currently held in S3, go to Destinations and click on List files next to S3 Backup. You can either download locally, restore to the site right away or delete them from here.

This will actually show all files that are currently stored in your S3 Bucket. If you only want to see database files from the current site in this list, then you'll need to create a dedicated S3 Bucket for it.

Using the database locally

One of the advantages of using Backup and Migrate is that you can easily pull down a database from the live site and restore it to your local development site. That way, you get the same content that the live site has.

Wrapping up

Having a robust backup strategy is a critical element in running a website. As you have been shown in this tutorial, there are tools to make this process relatively pain free to setup and run on auto pilot.

Feeling stuck with Drupal 8 module dev?

Get the free 7 lesson course that will help you get started today without feeling overwhelmed.

  • Create Drupal modules with just a few commands using the Drupal Console
  • Create custom pages
  • Create custom blocks
  • Create admin forms
  • Demystify routers and controllers
  • Bonus material

Find out more

Pages

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web