Transitioning from Drupal 7 to Drupal 8: programmatically creating blocks

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.

Comments

Nice tutorial Blair, much better using class methods instead of hooks! Thanks

Add new comment