Dynamically define blocks in Drupal 8 with derivatives

During the Drupal 8 port of my relatively new Mefibs module I realized that block creation in code got somewhat more complex than it used to be in Drupal 7 and earlier. It took me quite a while to figure out how this works, especially because there is not a lot of documentation for Drupal 8 yet. So I had to dig my way through core to see how it's done there and finally I got around it. The following is a comparison between how we used to do this in Drupal 7 and how it's done in Drupal 8.

Defining a single block

Let start with the ground work. In Drupal 8 blocks are now plugins. Defining a block in code is straight forward. You need to create a class located in lib/Drupal/mymodule/Plugin/Block/MyModuleBlock.php inside your module directory, containing:

<?php

/**
 * @file
 * Contains \Drupal\mymodule\Plugin\Block\MyModuleBlock.
 */

namespace Drupal\mymodule\Plugin\Block;
use Drupal\block\BlockBase;

/**
 * Provides a Custom block.
 *
 * @Block(
 *   id = "mymodule_block",
 *   admin_label = @Translation("MyModule block"),
 *   category = @Translation("MyModule")
 * )
 */
class MyModuleBlock extends BlockBase {
  /**
   * Build the content for mymodule block.
   */
  public function build() {
    return array(
      '#markup' => 'My awesome content',
    );
  }
}

This creates a single block that outputs only the string "My awesome content". As you can see, there is little code. The annotation in the comment, which is a new concept in Drupal 8, informs the plugin system about our custom block:

  • id: This defines the ID of the block which is used to identify the block. This is also used in HTML id attributes and class names.
  • admin_label: This designates the label used on the block configuration page.
  • category: This allows to control in which category on the block configuration page the block appears. You can use one of the existing categories or define a custom new one.

Your block plugin class will usually extend BlockBase. Inspecting this class will give you an idea of other methods that can be used to achieve specific goals, like access control or custom setting forms for the block. In order to fully define and display a block it is sufficient though to implement the build() method from BlockPluginInterface.

VoilĂ , there is the block. So this is the equivalent of implementing hook_block_info() and hook_block_view() in Drupal 7.

Defining multiple blocks

As you may notice though, this only defines a single block. There is only one class with a single build() method and no obvious way of extending this to support multiple instances of the same block type. In Drupal 7 blocks are defined in hook_block_info(), which returns an array of block description arrays. So it would have been sufficient to return multiple blocks in an array, keyed by the block id, like this:

function mymodule_block_info() {
  $blocks = array();
  $myblocks = array(
    'mymodule_block_first' => t('MyModule Block: First'),
    'mymodule_block_second' => t('MyModule Block: Second'),
  );
  foreach ($myblocks as $block_id => $block_label) {
    $blocks[$block_id] = array(
      'info' => $block_label,
      'cache' => DRUPAL_NO_CACHE,
    );
  }
  return $blocks;
}

In this example the available blocks are defined directly in the hook implementation, but they could also come from a database query or from another source, you name it. The important aspect is, that the logic to define multiple blocks was the same as for defining a single block. You just returned more of them. As they all had their proper block ids, which are passed to hook_block_view() or hook_block_configure() they could all be handled independently, but still be defined using the same logic.

Now in Drupal 8 this got more complicated. It took me a while, but finally I found a simple example in the core system module that defines menu blocks. A menu block is structurally exactly what I was after: A block definition that adheres to the same logic, though it can be created by a user in the UI so that you never know in advance how many blocks there will be or what their block ids might be.

That is where derivatives appear on the scene. Might be that this is obvious for someone coming from a strong OO background, but I had never heard of them before, so they didn't catch my attention on the first screening of the core block implementations. For those of you who, just like me, do not know what derivatives are, I will cite a phrase from the documentation:
"Derivatives provide a simple way to expand a single plugin so that it can represent itself as multiple plugins in the user interface."

Gotcha! Cool, so how are they used? It boils down to this:

  • Create a new class called, let's say, MyModuleBlock and save that in lib/Drupal/mymodule/Plugin/Derivative/MyModuleBlock.php. This class must implement DerivativeInterface and can extend DerivativeBase for convenience.
  • Implement the interface method getDerivativeDefinitions(). You can also implement getDerivativeDefinition() but it's usually not necessary when the derivative class extends DerivativeBase.
  • Reference the derivative class in the annotation of your block plugin class.
  • In the block plugin class, use $this->getDerivativeId() wherever you need the actual id of the block instance that is processed.

To extend the first example, the block plugin class would look something like this:

<?php

/**
 * @file
 * Contains \Drupal\mymodule\Plugin\Block\MyModuleBlock.
 */

namespace Drupal\mymodule\Plugin\Block;
use Drupal\block\BlockBase;

/**
 * Provides a Custom block.
 *
 * @Block(
 *   id = "mymodule_block",
 *   admin_label = @Translation("MyModule block"),
 *   category = @Translation("MyModule"),
 *   derivative = "Drupal\mymodule\Plugin\Derivative\MyModuleBlock"
 * )
 */
class MyModuleBlock extends BlockBase {
  /**
   * Build the content for mymodule block.
   */
  public function build() {
    $block_id = $this->getDerivativeId();
    return array(
      '#markup' => mymodule_build_block_content($block_id),
    );
  }
}

It didn't change much. We added the derivative class to the annotation and we retrieve the block id to pass it on to a function that retrieves content based on the given argument. That's it.

Now for the derivative class, we will simply use the logic from the earlier Drupal 7 example:

<?php

/**
 * @file
 * Contains \Drupal\mymodule\Plugin\Derivative\MyModuleBlock.
 */

namespace Drupal\mymodule\Plugin\Derivative;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface;

/**
 * Provides block plugin definitions for mymodule blocks.
 *
 * @see \Drupal\mymodule\Plugin\Block\MyModuleBlock
 */
class MyModuleBlock extends DerivativeBase implements ContainerDerivativeInterface {
  /**
   * {@inheritdoc}
   */
  public function getDerivativeDefinitions(array $base_plugin_definition) {
    $myblocks = array(
      'mymodule_block_first' => t('MyModule Block: First'),
      'mymodule_block_second' => t('MyModule Block: Second'),
    );
    foreach ($myblocks as $block_id => $block_label) {
      $this->derivatives[$block_id] = $base_plugin_definition;
      $this->derivatives[$block_id]['admin_label'] = $block_label;
      $this->derivatives[$block_id]['cache'] = DRUPAL_NO_CACHE;
    }
    return $this->derivatives;
  }
}

As you can see, this is very similar to the way it had been done in Drupal 7 in hook_block_info(). The form changes, but essentially we return an array with all our block definitions, keyed by an internal block ID that allows us to differentiate which block we are processing.

The given example is very simplistic and you wouldn't use derivatives for this, because obviously you already know what blocks will be needed when you write the code. Instead you would create two block plugins in separate classes. But it should make the basic idea of derivatives clear. The getDerivativeDefinitions() method from the system module for example looks like this:

/**
   * {@inheritdoc}
   */
  public function getDerivativeDefinitions(array $base_plugin_definition) {
    foreach ($this->menuStorage->loadMultiple() as $menu => $entity) {
      $this->derivatives[$menu] = $base_plugin_definition;
      $this->derivatives[$menu]['admin_label'] = $entity->label();
      $this->derivatives[$menu]['cache'] = DRUPAL_NO_CACHE;
    }
    return $this->derivatives;
  }

In this example it is obvious why the blocks can't simply be defined in different block plugin classes, because they are not known at the time the code is written. So instead it needs to load all available menu entities from the storage controller and create one block for each menu.

That's it. I hope it helps someone. If you find errors or want to share something, please leave a comment.

Category: 

Comments

Nice write-up!

Thanks for this. This helped me a lot to understand how to make custom made plugins discoverable. I was trying to swap out the aggregator module's parse and process plugins for my own versions. This was the missing piece of documentation I needed. Cheers.