Drupal 8 How to organise your hooks code in classes (Object-oriented way)

Profile picture for user a.berramou
Azz-eddine BERRAMOU 15 March, 2020

Sometimes you may find a .module file contain thousands lines of code so this file became not readable and hard to maintain.

So the question is: 

How can i organise my .module code to be more readable and maintainable?

The answer is to create a class for each hook and implement your logic there instead of implemented it inside your .module file:

To do so let implement an hook example like hook_form_FORM_ID_alter for node_article_edit_form.

So instead of do something like:

<?php

use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function MY_MODULE_form_node_article_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  // Your code here the two following lines just an examples.
  // Hide some fields.
  $form['field_SOME_FIELD_NAME']['#access'] = FALSE;
  // Attach some library ....
  $form['#attached']['library'][] = 'MY_MODULE/SOME_LIBRARY';
}

You can create a class called NodeArticleEditFormHandler inside your src folder like the following:

<?php

namespace Drupal\MY_MODULE;

use Drupal\Core\Form\FormStateInterface;

/**
 * Class NodeArticleEditFormHandler
 *
 * @package Drupal\MY_MODULE
 */
class NodeArticleEditFormHandler {

  /**
   * Alter Form.
   *
   * @param array $form
   *   Form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *    The current state of the form.
   * @param $form_id
   *    String representing the id of the form.
   */
  public function alterForm(array &$form, FormStateInterface $form_state, $form_id) {
    // Your code here the two following lines just an examples.
    // Hide some fields.
    $form['field_SOME_FIELD_NAME']['#access'] = FALSE;
    // Attach some library ....
    $form['#attached']['library'][] = 'MY_MODULE/SOME_LIBRARY';
  }

}

In case you need other services you can inject your dependencies by make your class implements ContainerInjectionInterface here is an example with current user service injection:
 

<?php

namespace Drupal\MY_MODULE;

use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Class NodeArticleEditFormHandler
 *
 * @package Drupal\MY_MODULE
 */
class NodeArticleEditFormHandler implements ContainerInjectionInterface {

  /**
   * The current user account.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * NodeArticleEditFormHandler constructor.
   *
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   The current user.
   */
  public function __construct(AccountProxyInterface $current_user) {
    $this->currentUser = $current_user;
  }

  /**
   * @inheritDoc
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('current_user')
    );
  }

  /**
   * Alter Form.
   *
   * @param array $form
   *   Form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *    The current state of the form.
   * @param $form_id
   *    String representing the id of the form.
   */
  public function alterForm(array &$form, FormStateInterface $form_state, $form_id) {
    // Example to get current user.
    $currentUser = $this->currentUser;
    // Your code here the two following lines just an examples.
    // Hide some fields.
    $form['field_SOME_FIELD_NAME']['#access'] = FALSE;
    // Attach some library ....
    $form['#attached']['library'][] = 'MY_MODULE/SOME_LIBRARY';
  }

}

And after that change your hook into:
 

<?php 

use Drupal\MY_MODULE\NodeArticleEditFormHandler;

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function MY_MODULE_form_node_article_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  return \Drupal::service('class_resolver')
    ->getInstanceFromDefinition(NodeArticleEditFormHandler::class)
    ->alterForm($form, $form_state, $form_id);
}

We are done!
Now your .module file more clean, readable and maintainable with less code on it.
You can do that to every hook for instance EntityHandler like:
 

<?php

use Drupal\MY_MODULE\EntityHandler;

/**
 * Implements hook_entity_presave().
 */
function MY_MODULE_entity_presave(EntityInterface $entity) {
  return \Drupal::service('class_resolver')
    ->getInstanceFromDefinition(EntityHandler::class)
    ->entityPresave($entity);
}

And so on!