Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Drupal 10 Module Development

You're reading from   Drupal 10 Module Development Develop and deliver engaging and intuitive enterprise-level apps

Arrow left icon
Product type Paperback
Published in Apr 2023
Publisher Packt
ISBN-13 9781837631803
Length 580 pages
Edition 4th Edition
Languages
Tools
Concepts
Arrow right icon
Author (1):
Arrow left icon
Daniel Sipos Daniel Sipos
Author Profile Icon Daniel Sipos
Daniel Sipos
Arrow right icon
View More author details
Toc

Table of Contents (21) Chapters Close

Preface 1. Chapter 1: Developing for Drupal 2. Chapter 2: Creating Your First Module FREE CHAPTER 3. Chapter 3: Logging and Mailing 4. Chapter 4: Theming 5. Chapter 5: Menus and Menu Links 6. Chapter 6: Data Modeling and Storage 7. Chapter 7: Your Own Custom Entity and Plugin Types 8. Chapter 8: The Database API 9. Chapter 9: Custom Fields 10. Chapter 10: Access Control 11. Chapter 11: Caching 12. Chapter 12: JavaScript and the Ajax API 13. Chapter 13: Internationalization and Languages 14. Chapter 14: Batches, Queues, and Cron 15. Chapter 15: Views 16. Chapter 16: Working with Files and Images 17. Chapter 17: Automated Testing 18. Chapter 18: Drupal Security 19. Index 20. Other Books You May Enjoy

Blocks

Blocks are plugins. However, the blocks you create in the UI are content entities and the placement of blocks (of both types) in the block layout are configuration entities. So, the block system is a good example of how entities and plugins work hand in hand in Drupal. We will talk in more detail about plugin types and entities later in the book.

So, how do we create a custom block plugin? All we need is one class, placed in the right namespace—Drupal\module_name\Plugin\Block. In this case (with plugins), folder naming is important. The plugin discoverability is dependent on the plugin type itself, and this one has the Plugin\Block namespace bit in it. But enough talk; let’s create a simple block that just renders the same as our Controller did previously, and I will explain things along the way.

Our first block plugin

So, this is our plugin class—HelloWorldSalutationBlock—which does just that:

namespace Drupal\hello_world\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\
    ContainerInterface;
use Drupal\hello_world\HelloWorldSalutation;
/**
 * Hello World Salutation block.
 *
 * @Block(
 *  id = "hello_world_salutation_block",
 *  admin_label = @Translation("Hello world salutation"),
 * )
 */
class HelloWorldSalutationBlock extends BlockBase
    implements ContainerFactoryPluginInterface {
  /**
   * The salutation service.
   *
   * @var \Drupal\hello_world\HelloWorldSalutation
   */
  protected $salutation;
  /**
   * Constructs a HelloWorldSalutationBlock.
   */
  public function __construct(array $configuration,
    $plugin_id, $plugin_definition, HelloWorldSalutation
        $salutation) {
    parent::__construct($configuration, $plugin_id,
        $plugin_definition);
    $this->salutation = $salutation;
  }
  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface
    $container, array $configuration, $plugin_id,
        $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('hello_world.salutation')
    );
  }
  /**
   * {@inheritdoc}
   */
  public function build() {
    return [
      '#markup' => $this->salutation->getSalutation(),
    ];
  }
}

Before even going through the explanation, you should know that clearing the cache and placing this block through the block management UI will do what we wanted. However, let’s understand what is going on here first.

Perhaps the strangest thing you’ll note is the DocBlock comment at the top of the class. This is called an annotation and denotes that this class is a Block plugin and contains its static definition. As I mentioned in the first chapter, annotations are the most common discovery mechanisms for plugins in Drupal core. They will most likely be replaced by PHP attributes in Drupal 11 but for the moment, we still use them. In this case, the plugin definition we need is made up of an ID and an administration label.

Note

Properly defined plugin types have an AnnotationInterface implementation, which describes the properties that can or should be used in the annotation. So, if you are unsure as to what needs to be there, look for this class for that specific plugin type.

Then, we see that our class extends BlockBase and also implements Container FactoryPluginInterface. The former, like ControllerBase and FormBase we saw earlier, provides a number of helpful things a block plugin needs. However, we cannot really get around extending this class because block plugins are quite complex, working with things such as context and configuration. So, ensure that you always extend this class. The latter is, however, optional. That interface makes this block plugin container-aware, that is, at the moment of instantiation, it uses the create() method to build itself using the service container for dependencies. And sure enough, we have our create() method as well.

Before moving on to the actual block building, we need to talk a bit about dependency injection in plugins. As you can see, the signature of this create() method is different from the one we saw in the Controller. This is also why we are using a different container-aware interface. The reason is that plugins are constructed with a few extra parameters: $configuration, $plugin_id, and $plugin_definition. The first contains any configuration values that are needed by the plugin, the second is the ID set in the plugin annotation (or other discovery mechanisms), and the third is an array that contains the metadata of this plugin (including all the info found in the annotation). However, apart from this, it’s business as usual when it comes to dependency injection. If a plugin type base class doesn’t implement this interface, you can do so yourself directly in your plugin. This works with most plugins, save for a few exceptions that cannot be made container-aware, but this happens very rarely.

Finally, we have a build() method, which is responsible for building the block content. It needs to return a render array (just like our Controller did), and as you can see, we are using our injected service and returning the same greeting. That is pretty much what we need to do in order to achieve our goal. There are other important aspects to block plugins we will cover later, such as caching and access, but we have specific chapters for those topics.

Block configuration

Before we close the book on our custom block plugin, let’s take a look at how we can add a configuration form to it. This way, we can practice using some more Form API elements and see how we can store and use block configuration.

Even though our functionality is complete (for the moment), let’s imagine that we need a Boolean-like control on our block configuration so that when an administrator places the block, they can toggle something, and that value can be used in the build() method. We could achieve this with three to four methods in our plugin class.

First, we would need to implement the defaultConfiguration() method, in which we describe the items of configuration that we are storing for this block and the default values for these items. So, we could have something like this:

/**
 * {@inheritdoc}
 */
public function defaultConfiguration() {
  return [
    'enabled' => 1,
  ];
}

We return an array of keys and values that will be in the configuration. Also, since we said we are going with a Boolean field, we use the number 1 as the value of a fictitious key named enabled.

Next, we would need to implement the blockForm() method, which provides our form definition for this configuration item:

/**
 * {@inheritdoc}
 */
public function blockForm($form, FormStateInterface
    $form_state) {
  $config = $this->getConfiguration();
  $form['enabled'] = array(
    '#type' => 'checkbox',
    '#title' => $this->t('Enabled'),
    '#description' => $this->t('Check this box if you want
        to enable this feature.'),
    '#default_value' => $config['enabled'],
  );
  return $form;
}

With the appropriate extra use statement at the top of the file:

use Drupal\Core\Form\FormStateInterface;

As you can see, this is a typical Form API definition for one form element of the checkbox type. Additionally, we are using the handy getConfiguration() method of the parent class to load up the configuration values that get saved with this block. If none have been saved, note that the enabled key will be present in it with the default value we set above (1).

Lastly, we would need the submit handler that will do what’s necessary to “store” the configuration. I used inverted commas because we don’t actually have to do anything related to storage, but just map the value submitted in the form to the relevant key in the configuration. The block system does it for us:

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

It couldn’t be simpler than this. Now, if we placed our custom block somewhere, the form we are presented with would incorporate our form element that allows us to toggle the enabled key. What remains to be done is to make use of this value inside the build() method. We could do that similarly to how we loaded the configuration values inside the buildForm() method:

$config = $this->getConfiguration();

Alas, we don’t really need this configuration in our example block, so we won’t be adding it to our code. However, it is important for you to know how to do it, so we covered it here. Moreover, before moving on, I also want to specify that you can use an optional method to handle validation on the configuration form. The method name is blockValidate(); it has the same signature as blockSubmit() and works the same way as the validation handler we saw when we built our standalone form. So, I won’t repeat that here.

You have been reading a chapter from
Drupal 10 Module Development - Fourth Edition
Published in: Apr 2023
Publisher: Packt
ISBN-13: 9781837631803
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime