In the first installment of this article series on Drupal 8 module development we started with the basics. We’ve seen what files were needed to let Drupal know about our module, how the routing process works and how to create menu links programatically as configuration.
In this tutorial we are going to go a bit further with our sandbox module found in this repository and look at two new important pieces of functionality: blocks and forms. To this end, we will create a custom block that returns some configurable text. After that, we will create a simple form used to print out user submitted values to the screen.
Drupal 8 blocks
A cool new change to the block API in D8 has been a switch to making blocks more prominent, by making them plugins (a brand new concept). What this means is that they are reusable pieces of functionality (under the hood) as you can now create a block in the UI and reuse it across the site – you are no longer limited to using a block only one time.
Let’s go ahead and create a simple block type that prints to the screen Hello World! by default. All we need to work with is one class file located in the src/Plugin/Block
folder of our module’s root directory. Let’s call our new block type DemoBlock
, and naturally it needs to reside in a file called DemoBlock.php
. Inside this file, we can start with the following:
<?php
namespace Drupal\demo\Plugin\Block;
use Drupal\block\BlockBase;
use Drupal\Core\Session\AccountInterface;
/**
* Provides a 'Demo' block.
*
* @Block(
* id = "demo_block",
* admin_label = @Translation("Demo block"),
* )
*/
class DemoBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
return array(
'#markup' => $this->t('Hello World!'),
);
}
/**
* {@inheritdoc}
*/
public function access(AccountInterface $account) {
return $account->hasPermission('access content');
}
}
Like with all other class files we start by namespacing our class. Then we use the BlockBase
class so that we can extend it, as well as the AccountInterface
class so that we can get access to the currently logged in user. Then follows something you definitely have not seen in Drupal 7: annotations.
Annotations are a PHP discovery tool located in the comment block of the same file as the class definition. Using these annotations we let Drupal know that we want to register a new block type (@Block
) with the id of demo_block
and the admin_label
of Demo block (passed through the translation system).
Next, we extend the BlockBase
class into our own DemoBlock
, inside of which we implement two methods (the most common ones you’ll implement). The build()
method is the most important as it returns a renderable array the block will print out. The access()
method controls access rights for viewing this block. The parameter passed to it is an instance of the AccountInterface
class which will be in this case the current user.
Another interesting thing to note is that we are no longer using the t()
function globally for translation but we reference the t()
method implemented in the class parent.
And that’s it, you can clear the caches and go to the Block layout
configuration page. The cool thing is that you have the block types on the right (that you can filter through) and you can place one or more blocks of those types to various regions on the site.
Drupal 8 block configuration
Now that we’ve seen how to create a new block type to use from the UI, let’s tap further into the API and add a configuration form for it. We will make it so that you can edit the block, specify a name in a textfield and then the block will say hello to that name rather than the world.
First, we’ll need to define the form that contains our textfield. So inside our DemoBlock
class we can add a new method called blockForm()
:
/**
* {@inheritdoc}
*/
public function blockForm($form, &$form_state) {
$form = parent::blockForm($form, $form_state);
$config = $this->getConfiguration();
$form['demo_block_settings'] = array(
'#type' => 'textfield',
'#title' => $this->t('Who'),
'#description' => $this->t('Who do you want to say hello to?'),
'#default_value' => isset($config['demo_block_settings']) ? $config['demo_block_settings'] : '',
);
return $form;
}
This form API implementation should look very familiar from Drupal 7. There are, however, some new things going on here. First, we retrieve the $form
array from the parent class (so we are building on the existing form by adding our own field). Standard OOP stuff. Then, we retrieve and store the configuration for this block. The BlockBase
class defines the getConfiguration()
method that does this for us. And we place the demo_block_settings
value as the #default_value
in case it has been set already.
Next, it’s time for the submit handler of this form that will process the value of our field and store it in the block’s configuration:
/**
* {@inheritdoc}
*/
public function blockSubmit($form, &$form_state) {
$this->setConfigurationValue('demo_block_settings', $form_state['values']['demo_block_settings']);
}
This method also goes inside the DemoBlock
class and all it does is save the value of the demo_block_settings
field as a new item in the block’s configuration (keyed by the same name for consistency).
Lastly, we need to adapt our build()
method to include the name to say hello to:
/**
* {@inheritdoc}
*/
public function build() {
$config = $this->getConfiguration();
if (isset($config['demo_block_settings']) && !empty($config['demo_block_settings'])) {
$name = $config['demo_block_settings'];
}
else {
$name = $this->t('to no one');
}
return array(
'#markup' => $this->t('Hello @name!', array('@name' => $name)),
);
}
By now, this should look fairly easy. We are retrieving the block’s configuration and if the value of our field is set, we use it for the printed statement. If not, use use a generic one. You can clear the cache and test it out by editing the block you assigned to a region and add a name to say hello to. One thing to keep in mind is that you are still responsible for sanitizing user input upon printing to the screen. I have not included these steps for brevity.
Drupal 8 forms
The last thing we are going to explore in this tutorial is how to create a simple form. Due to space limitations, I will not cover the configuration management aspect of it (storing configuration values submitted through forms). Rather, I will illustrate a simple form definition, the values submitted being simply printed on the screen to show that it works.
In Drupal 8, form definition functions are all grouped together inside a class. So let’s define our simple DemoForm
class inside src/Form/DemoForm.php
:
<?php
/**
* @file
* Contains \Drupal\demo\Form\DemoForm.
*/
namespace Drupal\demo\Form;
use Drupal\Core\Form\FormBase;
class DemoForm extends FormBase {
/**
* {@inheritdoc}.
*/
public function getFormId() {
return 'demo_form';
}
/**
* {@inheritdoc}.
*/
public function buildForm(array $form, array &$form_state) {
$form['email'] = array(
'#type' => 'email',
'#title' => $this->t('Your .com email address.')
);
$form['show'] = array(
'#type' => 'submit',
'#value' => $this->t('Submit'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, array &$form_state) {
if (strpos($form_state['values']['email'], '.com') === FALSE ) {
$this->setFormError('email', $form_state, $this->t('This is not a .com email address.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
drupal_set_message($this->t('Your email address is @email', array('@email' => $form_state['values']['email'])));
}
}
Apart from the OOP side of it, everything should look very familiar to Drupal 7. The Form API has remained pretty much unchanged (except for the addition of some new form elements and this class encapsulation). So what happens above?
First, we namespace the class and use the core FormBase
class so we can extend it with our own DemoForm
class. Then we implement 4 methods, 3 of which should look very familiar. The getFormId()
method is new and mandatory, used simply to return the machine name of the form. The buildForm()
method is again mandatory and it builds up the form. How? Just like you are used to from Drupal 7. The validateForm()
method is optional and its purpose should also be quite clear from D7. And finally, the submitForm()
method does the submission handling. Very logical and organised.
So what are we trying to achieve with this form? We have an email field (a new form element in Drupal 8) we want users to fill out. By default, Drupal checks whether the value input is in fact an email address. But in our validation function we make sure it is a .com
email address and if not, we set a form error on the field. Lastly, the submit handler just prints a message on the page.
One last thing we need to do in order to use this form is provide a route for it. So edit the demo.routing.yml
file and add the following:
demo.form:
path: '/demo/form'
defaults:
_form: '\Drupal\demo\Form\DemoForm'
_title: 'Demo Form'
requirements:
_permission: 'access content'
This should look familiar from the previous article in which we routed a simple page. The only big difference is that instead of _content
under defaults
, we use _form
to specify that the target is a form class. And the value is therefore the class name we just created.
Clear the caches and navigate to demo/form
to see the form and test it out.
If you are familiar with drupal_get_form()
and are wondering how to load a form like we used to in Drupal 7, the answer is in the global Drupal class. Thus to retrieve a form, you can use its formBuilder()
method and do something like this:
$form = \Drupal::formBuilder()->getForm('Drupal\demo\Form\DemoForm');
Then you can return $form
which will be the renderable array of the form.
Conclusion
In this article we’ve continued our exploration of Drupal 8 module development with two new topics: blocks and forms. We’ve seen how to create our own block type we can use to create blocks in the UI. We’ve also learned how to add a custom configuration to it and store the values for later use. On the topic of forms, we’ve seen a simple implementation of the FormBase
class that we used to print out to the screen the value submitted by the user.
In the next tutorial we will take a quick look at configuration forms. We will save the values submitted by the user using the Drupal 8 configuration system. Additionally, we will look at the service container and dependency injection and how those work in Drupal 8. See you then.
Frequently Asked Questions (FAQs) about Building Drupal 8 Module: Blocks and Forms
What is the basic structure of a Drupal 8 module?
A Drupal 8 module is essentially a set of files that contain some functionality and is written in PHP. The basic structure of a Drupal 8 module includes a .info.yml file, a .module file, and other optional files like .css, .js, .twig, etc. The .info.yml file is used to list the module’s name, description, package, type, and core compatibility. The .module file is where the actual PHP code resides.
How can I create a custom block in Drupal 8?
Creating a custom block in Drupal 8 involves creating a new custom module and defining a block plugin in it. The block plugin is a PHP class file that defines the block’s properties and methods. It should be placed in the ‘src/Plugin/Block’ directory of your module. The block plugin class should extend the ‘BlockBase’ class and implement the ‘build()’ method, which returns a render array for the block content.
How can I create a custom form in Drupal 8?
Creating a custom form in Drupal 8 involves creating a new custom module and defining a form class in it. The form class is a PHP class file that defines the form’s properties and methods. It should be placed in the ‘src/Form’ directory of your module. The form class should extend the ‘FormBase’ class and implement three methods: ‘getFormId()’, ‘buildForm()’, and ‘submitForm()’. The ‘buildForm()’ method returns a form array, and the ‘submitForm()’ method handles the form submission.
How can I display a block in a specific region of my Drupal 8 site?
To display a block in a specific region of your Drupal 8 site, you need to go to the ‘Block layout’ page in the admin interface. Here, you can assign your block to any region of your theme. You can also configure the block’s visibility settings based on conditions like path, content type, user role, etc.
How can I validate the input of a custom form in Drupal 8?
To validate the input of a custom form in Drupal 8, you can override the ‘validateForm()’ method in your form class. This method is called before the ‘submitForm()’ method when the form is submitted. In the ‘validateForm()’ method, you can add your validation logic and call the ‘setError()’ method to set an error message for a form element if the validation fails.
How can I alter an existing form in Drupal 8?
To alter an existing form in Drupal 8, you can implement the ‘hook_form_FORM_ID_alter()’ function in your module. This function is called when the form is built, and it allows you to modify the form array. The ‘FORM_ID’ in the function name should be replaced with the ID of the form you want to alter.
How can I programmatically submit a form in Drupal 8?
To programmatically submit a form in Drupal 8, you can create an instance of your form class and call the ‘submitForm()’ method on it. However, before calling this method, you should prepare a form state object and set the values for the form elements in it.
How can I create a configuration form in Drupal 8?
To create a configuration form in Drupal 8, you can define a form class that extends the ‘ConfigFormBase’ class instead of the ‘FormBase’ class. The ‘ConfigFormBase’ class provides additional methods for handling configuration data, such as ‘getEditableConfigNames()’ and ‘config()’. The configuration data is stored in the Drupal configuration system and can be accessed from anywhere in your code.
How can I create a multistep form in Drupal 8?
To create a multistep form in Drupal 8, you can use the ‘FormStateInterface’ object to store the data between steps. In the ‘buildForm()’ method, you can check the current step in the form state and return a different form array for each step. In the ‘submitForm()’ method, you can check the current step and either store the data and go to the next step or process the final submission.
How can I create an AJAX form in Drupal 8?
To create an AJAX form in Drupal 8, you can add an ‘#ajax’ property to a form element in the ‘buildForm()’ method. This property should be an array that specifies the callback function to be called when the element is triggered. The callback function should return a part of the form to be updated or a set of AJAX commands.
Daniel Sipos is a Drupal developer who lives in Brussels, Belgium. He works professionally with Drupal but likes to use other PHP frameworks and technologies as well. He runs webomelette.com, a Drupal blog where he writes articles and tutorials about Drupal development, theming and site building.