Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

Summary

In Drupal 7, definition and altering of blocks are done through block specific mechanisms. A block was defined by implementing hook_block_info() providing the available deltas and the corresponding administrative labels. hook_block_view() was implemented to return the relevant render array. The block then could be configured in the UI to appear once per theme in a specific region -- the disabled blocks are visible in the UI in the "disabled" region.

In Drupal 8, the logic for blocks is provided by so called plugins (annotated classes in a specific namespace), the annotation providing the administrative label. The concept of delta is gone, the plugin is identified by its plugin id. The defining module does not matter, the plugin id must be unique across all modules. The build method of the plugin class provides the relevant render array. The block does not appear at all until it is placed; however it can be placed any number of times, it can appear in a theme any number of times. The placement information (theme, region, weight) and any block specific configuration information is saved in a configuration entity. This is the same plugin/configuration entity dual mechanism that is used for actions, editors, migrations, views etc.

API changes

  • hook_block_info() has been removed. Block types are now defined by adding a block plugin.
  • hook_block_configure(), hook_block_save(), and hook_block_view() have been replaced with block plugin methods.
  • hook_block_info_alter() has been removed. The administrative label can be changed with the new hook_block_alter(). Most other properties (cache, visibility etc) can be changed on the configuration entity save or load: hook_block_presave, hook_block_insert, hook_block_update (or even hook_block_load for very dynamic cases).
  • hook_block_list_alter() has been removed. First, as with every entity, there is the entity access layer: hook_entity_access or hook_block_access(). This is the recommended way for most cases. Also, if the core provided access logic (roles, paths, language) is adequate then see above about changing the visibility property in the configuration enttity.
  • hook_block_MODULE_DELTA_alter() has been replaced with hook_block_ID_alter() and hook_block_NAME_alter()

The above are the hooks normal modules should implement. However, in some cases overriding the default behaviors might be necessary and with Drupal 8 this is possible. At the end of this change notice we will list these.

Examples

Defining a block type

To define a block type, place a MyBlock.php plugin class file in the following directory structure:
my_module/lib/Drupal/my_module/Plugin/Block
(See the explanation of the PSR-0 standard for more information.) This plugin class will be automatically discovered and loaded when my_module is enabled.

Drupal 7

In book.module:

<?php
function book_block_info() {
 
$block = array();
 
$block['navigation']['info'] = t('Book navigation');
 
$block['navigation']['cache'] = DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE;
  return
$block;
}
?>

Drupal 8

The relevant parts of core/modules/search/lib/Drupal/search/Plugin/Block/SearchBlock.php:

<?php
/**
* @file
* Contains \Drupal\search\Plugin\Block\SearchBlock.
*/
namespace Drupal\search\Plugin\Block;
use
Drupal\Core\Session\AccountInterface;
use
Drupal\block\BlockBase;
/**
* Provides a 'Search form' block.
*
* @Block(
*   id = "search_form_block",
*   admin_label = @Translation("Search form"),
*   category = @Translation("Forms")
* )
*/
class SearchBlock extends BlockBase {
 
/**
   * {@inheritdoc}
   */
 
public function access(AccountInterface $account) {
    return
$account->hasPermission('search content');
  }
 
/**
   * {@inheritdoc}
   */
 
public function build() {
    return \
Drupal::formBuilder()->getForm('Drupal\search\Form\SearchBlockForm');
  }
}
?>

The administration label hook_block_info() is now added to the block plugin's annotation (a special section at the end of the documentation block wrapped in @Block()). (More about annotations and plugin discovery). The information in the annotation (for example, category) is not editable in the UI and is the same for every placement. If some data, like the book block mode can change between placements then it needs to go in the defaultConfiguration() method.

Configuration settings are declared in a defaultSettings() method; instance settings are declared in a defaultInstanceSettings() method in the configuration entity. @see #2136197: Move field/instance/widget/formatter settings out of annotation / plugin definition. No longer applicable: This method was called "settings" and was changed in #2062573: Add method defaultConfiguration() in ConfigurablePluginInterface, see also this change notice [#2088589].

The "admin_label" key was originally called "subject", it was changed in #1591806: Change block "subject" so that it's called a (admin_)label like everything else on the theme layer, and block plugins were given their own separate annotation in #2065721: Add a dedicated @Block plugin annotation.

Adding configuration options to a block type's form

Drupal 7

In book.module:

<?php
function book_block_configure($delta = '') {
 
$form['book_block_mode'] = array(
// form definition omitted for brevity.
 
);
  return
$form;
}
function
book_block_save($delta = '', $edit = array()) {
 
$block = array();
 
variable_set('book_block_mode', $edit['book_block_mode']);
}
?>

Drupal 8

In core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php:

<?php
 
function blockForm($form, &$form_state) {
   
$form['book_block_mode'] = array(
     
'#default_value' => $this->configuration['block_mode'],
// rest of the form omitted for brevity.
   
);
    return
$form;
  }
  public function
blockSubmit($form, &$form_state) {
   
$this->configuration['block_mode'] = $form_state['values']['book_block_mode'];
  }
?>

Note that these added form elements allow the user to configure the settings added in defaultConfiguration(). Also note blockSubmit merely sets the configuration; the actual persisting of the configuration is done automatically.

Defining a block type's output

Drupal 7

The user login block is a much better example for this one. In user.module:

<?php
function user_block_view($delta = '') {
 
$block = array();
  switch (
$delta) {
    case
'login':
      if (!
$user->uid && !(arg(0) == 'user' && !is_numeric(arg(1)))) {
       
$block['subject'] = t('User login');
       
$block['content'] = drupal_get_form('user_login_block');
      }
  }
  return
$block;
?>

Drupal 8

In core/modules/user/lib/Drupal/user/Plugin/Block/UserLoginBlock.php:

<?php
class UserLoginBlock extends BlockBase {
 
/**
   * {@inheritdoc}
   */
 
public function access(AccountInterface $account) {
    return (!
$account->id() && !(arg(0) == 'user' && !is_numeric(arg(1))));
  }
 
/**
   * {@inheritdoc}
   */
 
public function build() {
   
// Building of the form is omitted.
   
return array(
     
'user_login_form' => $form,
     
'user_links' => array(
       
'#theme' => 'item_list',
       
'#items' => $items,
      ),
    );
?>

Note how access replaced the if – instead of returning an empty array, the build function is simply not called. This results in much cleaner code. (However, the build function can still return an empty array if it wants the block skipped.)

Altering the visibility of other modules' blocks

Drupal 7

In node.module:

<?php
/**
* Implements hook_block_list_alter().
*/
function node_block_list_alter(&$blocks) {
  foreach (
$blocks as $key => $block) {
// ...
     
else {
       
// This is not a node page, remove the block.
       
unset($blocks[$key]);
        continue;
      }
  }
}
?>

Drupal 8

In node.module:

<?php
/**
* Implements hook_block_access().
*/
function node_block_access($block) {
// …
   
if (empty($allowed_types)) {
     
// There are no node types selected in visibility settings so there is
      // nothing to do.
     
return;
    }
// …
   
return FALSE;
}
?>

Note how the new hook only gets one block and it can return FALSE to deny access or NULL if it doesn't want to make a decision on access.

The meta hooks / mechanisms

As mentioned above, the default behaviors are modifiable via various mechanisms, mostly hooks. The need to use these mechanisms should arise very rarely and even then extreme caution is advised. If two modules try to replace the same functionality, conflicts might arise. Most modules (and developers) will expect the default mechanisms to work. Don't forget: with great power comes great responsibility.

  • By using hook_block_alter it is possible to change the class belonging to a given plugin id.
  • hook_block_alter itself is fired by the block plugin manager which can be replaced: it is a service and services can be altered by a ModulenameServiceProvider class implementing the ServiceModifierInterface.
  • By using hook_entity_info_alter it is possible to change the access, the storage and the view controller of the Block entity type.
Impacts: 
Site builders, administrators, editors
Module developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done

Comments

Note that blocks no longer appear automatically on the Blocks page - they have to be "Placed".

If you need to set your block's title dynamically, or force override, you can manually set $this->configuration['label'] inside the build() method. Views blocks currently do this.

Senior Drupal Developer for Lullabot | www.davereid.net | certifiedtorock.com/u/53892