4.4 C
Madrid
lunes, abril 1, 2024
spot_img

Drupal 8 Custom Plugin Types – SitePoint

Drupal 8 comes with a great addition to the backend developer toolkit in the form of the plugin system. Completely new, specific to Drupal and evolved from serving only a few specific purposes, plugins have become the go-to system for reusable functionality in Drupal 8.

In this article series of two parts, we will use this system to build a feature that allows the use of custom forms together with node entities. After we’re done, we’ll be able to do the following:

  • configure node bundles to use one of multiple form types to be displayed together with the node display
  • easily define new form types by extending from a sensible base class

Because the topic is very well covered elsewhere, I will not go into the details of how plugins work. But do feel free to brush up on the theory before diving into the crux of it here. And if you want to take a look at the end result, the code we write in both articles can be found in this repository.

We will get started by creating our custom plugin type. To this end, we will have 2 interfaces and 6 classes. It sounds like much, but I assure you they are rather boilerplate and quick to set up. Then, in the next installment of this series, we will see how to use it for our reusable forms attached to nodes.

Plugin manager

Responsible for discovering and loading plugins, the most important part in any plugin type is the manager. However, it’s very simple to create one because Drupal already provides us with a sensible default base to extend. So in our module’s /src folder we can have this class inside a ReusableFormManager.php file (the de facto name of our plugin type becoming ReusableForm):

php
namespace Drupalreusable_forms;

use DrupalCorePluginDefaultPluginManager;
use DrupalCoreCacheCacheBackendInterface;
use DrupalCoreExtensionModuleHandlerInterface;

class ReusableFormsManager extends DefaultPluginManager {

public function __construct(Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent
::__construct(‘Plugin/ReusableForm’, $namespaces, $module_handler, ‘Drupalreusable_formsReusableFormPluginInterface’, ‘Drupalreusable_formsAnnotationReusableForm’);
$this
->alterInfo(‘reusable_forms_info’);
$this
->setCacheBackend($cache_backend, ‘reusable_forms’);
}
}

As I mentioned, our manager extends the DefaultPluginManager class and just overrides the constructor to call the parent one with some important information about our plugin type:

  • Plugin/ReusableForm – the subdirectory where plugins of this type will be found within any module
  • Drupalreusable_formsReusableFormPluginInterface – the interface each of our plugins will need to implement
  • Drupalreusable_formsAnnotationReusableForm – the annotation class that will define our plugin properties (such as ID, name, etc.)

Additionally, we create an alter hook which can be implemented by various modules to alter the plugin definitions and we set a key for our plugins in the cache backend. For more information about plugin managers, what they do and how they are set up, you should consult the documentation page available on Drupal.org.

Plugin interface

Next, let’s create that interface the manager expects all our plugins to implement. Inside a file called ReusableFormPluginInterface.php located in the src/ folder of our module, we can have this:

php

namespace Drupalreusable_forms;

use DrupalCoreEntityEntityInterface;
use DrupalCorePluginContainerFactoryPluginInterface;
use DrupalComponentPluginPluginInspectionInterface;

interface ReusableFormPluginInterface extends PluginInspectionInterface, ContainerFactoryPluginInterface {

/**
* Return the name of the reusable form plugin.
*
* @return string
*/

public function getName();

/**
* Builds the associated form.
*
* @param $entity EntityInterface.
* The entity this plugin is associated with.
*
* @return array().
* Render array of form that implements Drupalreusable_formsFormReusableFormInterface
*/

public function buildForm($entity);
}

This is a very simple interface that enforces only two methods: getName() and buildForm(). The first will return the name of the plugin while the latter is expected to be passed an entity object and to return a render array of a form definition that implements Drupalreusable_formsFormReusableFormInterface (the interface we will set up for our actual forms). You’ll also notice that we are extending two other interfaces. Those provide us with some extra helpful methods and allow us to inject dependencies from the container.

Plugin annotation

As defined in the manager, let’s also set up our annotation class inside src/Annotation/ReusableForm.php:

php

namespace Drupalreusable_formsAnnotation;

use DrupalComponentAnnotationPlugin;

/**
* Defines a reusable form plugin annotation object.
*
* @Annotation
*/

class ReusableForm extends Plugin {

/**
* The plugin ID.
*
* @var string
*/

public $id;

/**
* The name of the form plugin.
*
* @var DrupalCoreAnnotationTranslation
*
* @ingroup plugin_translatable
*/

public $name;

/**
* The form class associated with this plugin
*
* It must implement Drupalreusable_formsFormReusableFormInterface.
*
* @var string
*/

public $form;
}

Here we simply extend the default Plugin annotation class and define three properties (id, name and form). These will be the three keys found in the annotation of our individual plugins (we’ll see an example in part two of this series).

Plugin base

So far we have the core of what we need for our plugin type: a plugin manager that can discover new plugins using the annotation we defined and instantiate them with its default factory, i.e. the Container Factory.

Let us now lay the ground work for the plugins themselves by creating a base class all the plugins can/should/will extend. Inside the src/ folder of our module we can create a new file called ReusableFormPluginBase.php with the following abstract class inside:

php

namespace Drupalreusable_forms;

use DrupalComponentPluginPluginBase;
use DrupalCoreFormFormBuilder;
use SymfonyComponentDependencyInjectionContainerInterface;

abstract class ReusableFormPluginBase extends PluginBase implements ReusableFormPluginInterface {

/**
* The form builder.
*
* @var DrupalCoreFormFormBuilder.
*/

protected $formBuilder;

/**
* Constructs a ReusableFormPluginBase object.
*
* @param array $configuration
* @param string $plugin_id
* @param mixed $plugin_definition
* @param FormBuilder $form_builder
*/

public function __construct(array $configuration, $plugin_id, $plugin_definition, FormBuilder $form_builder) {
parent
::__construct($configuration, $plugin_id, $plugin_definition);
$this
->formBuilder = $form_builder;
}

/**
* {@inheritdoc}
*/

public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration
,
$plugin_id
,
$plugin_definition
,
$container
->get(‘form_builder’)
);
}

/**
* {@inheritdoc}
*/

public function getName() {
return $this->pluginDefinition[‘name’];
}

/**
* {@inheritdoc}
*/

public function buildForm($entity) {
return $this->formBuilder->getForm($this->pluginDefinition[‘form’], $entity);
}
}

There are a few things to note here. First, we are extending from the plugin base class provided by Drupal so that we get some useful functionality (the methods of PluginInspectionInterface are already implemented with sensible defaults). Second, we are implementing the interface we defined earlier. All of our plugins need to implement it so might as well take care of it here. Third, we are using dependency injection to load from the container the form_builder service we will need to build our forms. This is possible because our interface extends from the ContainerFactoryPluginInterface.

For more information about the service container and dependency injection in Drupal 8, check out one of my previous articles on Sitepoint.com.

As our interface dictates, we already take care of implementing the two methods right here in our base class. The getName() method will simply return the name of the plugin as defined in the plugin annotation. The buildForm() method, on the other hand, will use the form builder to build a form. For this it will use the class provided in the plugin annotation’s form key (which needs to be the fully qualified name of a class that implements our Form interface which we haven’t defined yet). In doing so, we also pass the $entity argument to the form (whatever that may be as long as it implements EntityInterface). This is so that the form that is being rendered on the node page becomes aware of the node it is being rendered with.

Form Interface

Our plugin type is pretty much finished. We can now provide some sensible defaults to the forms that will be used by these plugins. Inside src/Form/ReusableFormInterface.php we can have this simple interface:

php

namespace Drupalreusable_formsForm;

use DrupalCoreFormFormInterface;

interface ReusableFormInterface extends FormInterface {}

We are doing nothing here except for extending from the default Drupal FormInterface. We have it so that we can add whatever methods we need our forms to implement (currently none) and are able to identify forms that implement this interface as compatible with our plugins.

Form base

Now that we have a form interface to implement, let’s also create a form base class that the rest of the forms can extend. Inside src/Form/ReusableFormBase.php we can have the following abstract class:

php

namespace Drupalreusable_formsForm;

use DrupalCoreEntityEntityInterface;
use DrupalCoreFormFormBase;
use DrupalCoreFormFormStateInterface;

/**
* Defines the ReusableFormBase abstract class
*/

abstract class ReusableFormBase extends FormBase implements ReusableFormInterface {

/**
* @var EntityInterface.
*/

protected $entity;

/**
* {@inheritdoc}.
*/

public function buildForm(array $form, FormStateInterface $form_state) {

$build_info = $form_state->getBuildInfo();
if ($build_info[‘args’] && $build_info[‘args’][0] instanceof EntityInterface) {
$this
->entity = $build_info[‘args’][0];
}

$form[‘first_name’] = array(
‘#type’ => ‘textfield’,
‘#title’ => $this->t(‘First name’),
);

$form[‘last_name’] = array(
‘#type’ => ‘textfield’,
‘#title’ => $this->t(‘Last name’),
);

$form[‘email’] = array(
‘#type’ => ‘email’,
‘#title’ => $this->t(‘Email’),
);

$form[‘actions’][‘#type’] = ‘actions’;
$form
[‘actions’][‘submit’] = array(
‘#type’ => ‘submit’,
‘#value’ => $this->t(‘Submit’),
‘#button_type’ => ‘primary’,
);

return $form;
}
}

As you can see, we are implementing the interface but we are also extending from the default Drupal FormBase class. And in this example we only have the buildForm method that returns a simple form with three fields and a submit button. You can do whatever you want here in terms of what you consider a good base form. Additionally though, at the beginning of this method, we are checking if an EntityInterface object has been passed as an argument when this form is built and setting it as a protected property. The classes extending this will be able to make use of this entity as long as they call the parent::buildForm() method first.

Conclusion

In the first part of this article series we focused on setting up our custom plugin type and getting it ready for use. In doing so we quickly went through this process and saw how our plugins are expected to work with the actual form classes. In the next part we will work on making it possible to display them together with the nodes. This means adding extra configuration to the node type entities and displaying the forms using pseudo fields managed as part of the regular content view modes. Stay tuned!

Artículos relacionados

Dejar respuesta

Please enter your comment!
Please enter your name here

- Anuncio -spot_img

Últimos artículos

3,913SeguidoresSeguir
0suscriptoresSuscribirte