I thought I was done with the series of posts on object-oriented programming after my last one. Of course, there is a lot we can write about object-oriented programming and Drupal, but that post covers everything noteworthy from the example. There is a way which is old-school but works as it should and another which looks modern but comes with problems. Is there a middle-ground? Tim Plunkett responded on Twitter saying there is.
There's a third way! See https://t.co/cFizL60Loy and https://t.co/so2syXyXZS
— Tim Plunkett (@timplunkett) April 24, 2021
At the end of the last post, I mentioned that the problem is not with object-oriented programming. It is with using something for the sake of using it. If you understand something and use it judiciously, that is more likely to result in a robust solution (also maintainable). Let’s look at the approach mentioned in the tweet in detail.
The fundamental difference
The problem with the version with objects in the last post was not because it used objects. It was because it overrode the entry point of the form callback to change it. Using classes has its advantages, most notably that we can use dependency injection to get our dependencies. For more complex alterations, it is also useful to encapsulate the code in a single class. But the approach with the entry point made the solution unworkable.
On the other hand, the form_alter hook is actually designed for this purpose. Yes, it cannot be used within classes and you have to dump it in a module file along with all the other functions. But it works, and that’s more important. There is no alternative designed for this purpose. So, in a way, the fundamental difference is that this method works whereas the other doesn’t. It doesn’t matter that we can’t use nice things like dependency injection here if it doesn’t even work.
Bringing them together
The two worlds are not so disjoint; PHP handles both brilliantly, after all. If you want to encapsulate your code in objects, the straightforward solution is to write your code in a class and instantiate it from your form_alter hook. Yes, you still have the hook but it is only a couple of lines long at most and all your logic is neatly handled in a class where it is easy to read and test. The class might look something like this.
<?php
namespace Drupal\mymodule;
use Drupal\Core\Form\FormStateInterface;
class MySiteInfoFormAlter {
public function alterForm(array &$form, FormStateInterface $form_state, $form_id) {
// Add siteapikey text box to site information group.
$form['new_element'] = [ '#type' => 'textfield',
// ... more attributes
];
}
}
And you can simply call it from your hook like so:
function mymodule_form_system_site_information_settings_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
$alter = new \Drupal\mymodule\MySiteInfoFormAlter();
$alter->alterForm($form, $form_state, $form_id);
}
You can save the object instantiation if you make it static but let’s not go there (you lose all advantages of using objects if you do that).
Dependency Injection by Drupal
This is already looking better (and you don’t even need route subscribers). But let’s take it a step further to bring in dependency injection.
We can certainly pass in the dependencies we want from our hook when we create the object, but why not let Drupal do all that work? We have the class resolver service in Drupal that helps us create objects with dependencies. The class needs to implement ContainerInjectionInterface but that is a very common pattern in Drupal code. With such a class, you only need to create the object instance using the class resolver service to build it with dependencies.
function mymodule_form_system_site_information_settings_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
\Drupal::service('class_resolver')
->getInstanceFromDefinition(MySiteInfoFormAlter::class)
->alterForm($form, $form_state, $form_id);
}
For better examples, look at the links Tim Plunkett mentioned in the tweet: the hook for form_alter and the method.
I hope you found the example useful and a workable middle-ground. Do let me know what you think.