With over a decade of web development experience, I specialize in Drupal (7, 8, 9, 10), CodeIgniter, Laravel, and WordPress. I offer extensive expertise in both module and theme development, providing customized solutions for complex projects. Whether you need to enhance an existing platform, create new features, or seek expert guidance, I'm here to assist. My dedication to delivering high-quality, efficient, and scalable solutions is unmatched. Feel free to contact me to explore how I can contribute to your project's success. Let's turn your ideas into reality!

“Learn to implement a robust Object-Oriented Programming (OOP) batch system in Drupal 9 for efficient data processing. Example code included.”

This blog post dives into the world of Object-Oriented Programming (OOP) in Drupal 9, guiding readers through the creation of a clean and efficient batch system. The tutorial covers key OOP principles, design patterns, and Drupal-specific best practices to ensure a modular and maintainable solution. Readers will gain insights into structuring code, managing dependencies, and optimizing performance.

While anticipating Drupal 9's core implementation of OOP Batches, let's create a starting point for them. This example is designed for Drupal 9+ (PHP 7.4+).

Module Setup:

  1. Create Module:

    • Location: web/modules/custom
    • Folder: module_example
    • File: /module_example/module_example.module
    <?php
    /*
     * @file
    */
    
  2. Module Info:

    • File: /module_example/module_example.info.yml
    name: Module Example
    type: module
    description: Module example
    package: Module example
    core_version_requirement: ^9
  3. Routing:

    • File: /module_example/module_example.routing.yml
    module_example.admin_form.example_form:
      path: '/admin/config/system/module-example'
      defaults:
        _form: '\Drupal\module_example\Form\ExampleForm'
        _title: 'Form example'
      requirements:
        _user_is_logged_in: 'TRUE'
        _permission: 'administer site configuration'
  4. Service Definition:

    • File: /module_example/module_example.services.yml
    services:
      module_example.batch_example:
        class: Drupal\module_example\Batch\BatchExample
        arguments: ['@messenger', '@extension.list.module']
  5. Form Setup:

    • Folder: /module_example/src/Form
    • File: /module_example/src/Form/ExampleForm.php
    <?php
    
    namespace Drupal\module_example\Form;
    
    use Drupal\Core\Form\FormBase;
    use Drupal\Core\Form\FormStateInterface;
    use Symfony\Component\DependencyInjection\ContainerInterface;
    
    /**
     * Implements an Example form.
     */
    class ExampleForm extends FormBase {
    
      /**
       * Batch injection.
       *
       * @var \Drupal\module_example\Batch\BatchExample
       *   Grab the path from module_example.services.yml file.
       */
      protected $batch;
    
      /**
       * {@inheritdoc}
       */
      public static function create(ContainerInterface $container) {
        // Instantiates this form class.
        $instance = parent::create($container);
        // Put here injection of your batch service by name from module_example.services.yml file.
        $instance->batch = $container->get('module_example.batch_example');
    
        return $instance;
      }
    
      /**
       * {@inheritdoc}
       */
      public function getFormId() {
        // Any name for your form to indicate it.
        return 'example_of_form';
      }
    
      /**
       * {@inheritdoc}
       */
      public function buildForm(array $form, FormStateInterface $form_state) {
        // No need for t() in admin part.
        // Triggers submitForm() function.
        $form['submit'] = [
          '#type' => 'submit',
          '#prefix' => '<br>',
          '#value' => 'Start batch',
        ];
    
        return $form;
      }
    
      /**
       * {@inheritdoc}
       */
      public function submitForm(array &$form, FormStateInterface $form_state) {
        // As we don't need to change in our batch depending on inputs,
        // just passing empty array or removing param.
        $values = [];
        $this->batch->run($values);
      }
    
    }
  6. Batch Setup:

    • Folder: /module_example/src/Batch
    • File: /module_example/src/Batch/BatchExample.php
    <?php
    
    namespace Drupal\module_example\Batch;
    
    use Drupal\Core\Batch\BatchBuilder;
    use Drupal\Core\Messenger\MessengerInterface;
    use Drupal\Core\Extension\ModuleExtensionList;
    use Drupal\Core\DependencyInjection\DependencySerializationTrait;
    
    /**
     * Batch Example.
     */
    class BatchExample {
    
      use DependencySerializationTrait;
    
      /**
       * The messenger.
       *
       * @var \Drupal\Core\Messenger\MessengerInterface
       */
      protected $messenger;
    
      /**
       * Module extension list.
       *
       * @var \Drupal\Core\Extension\ModuleExtensionList
       */
      protected $moduleExtensionList;
    
      /**
       * Constructor.
       *
       * @param \Drupal\Core\Messenger\MessengerInterface $messenger
       *   The messenger.
       * @param \Drupal\Core\Extension\ModuleExtensionList $moduleExtensionList
       *   The module extension list.
       */
      public function __construct(
        MessengerInterface $messenger,
        ModuleExtensionList $moduleExtensionList
      ) {
        $this->messenger = $messenger;
        $this->moduleExtensionList = $moduleExtensionList;
      }
    
      /**
       * The Starting point for batch.
       *
       * @param $values
       *   If you need to change behavior of batch but not much, send values from external.
       */
      public function run($values) {
        // File needs to be placed /module_example/src/Batch or hardcode the name of module.
        $moduleName = basename(dirname(__DIR__, 2));
        $modulePath = $this->moduleExtensionList->getPath($moduleName);
        $batchBuilder = new BatchBuilder();
        $batchBuilder
          // Change batch name here.
          ->setTitle('Example batch')
          ->setInitMessage('Initializing. <br/><b style="color: #f00;">Navigating away will stop the process.</b>')
          ->setProgressMessage('Completed @current of @total. <br/><b style="color: #f00;">Navigating away will stop the process.</b>')
          ->setErrorMessage('Batch has encountered an error.')
          ->setFile($modulePath . '/src/Batch/' . basename(__FILE__));
    
        // Dummy data, grab data on this place.
        $items = [
          ['id' => 0, 'data' => 'item'],
          ['id' => 1, 'data' => 'item'],
          ['id' => 2, 'data' => 'item'],
        ];
        if (!empty($items)) {
          foreach ($items as $item) {
            // Adding operations that we will process on each item in a batch.
            $batchBuilder->addOperation([$this, 'process'], [
              $item,
              // Add how many variables you need here.
            ]);
          }
          $batchBuilder->setFinishCallback([$this, 'finish']);
          // Changing it to array that we can set it in functional way (only way on this moment).
          $batch = $batchBuilder->toArray();
          batch_set($batch);
        }
        else {
          $this->messenger->addMessage('No entities exists');
        }
      }
    
      /**
       * Batch processor.
       */
      public function process($item, &$context) {
        try {
          $id = $item['id'];
          // Display a progress message.
          $context['message'] = "Now processing {$id} entity...";
          // Body of the batch, logic that needs to be presented place here.
          $changed = FALSE;
          // For example we will change item data.
          if (!empty($item['data'])) {
            $item['data'] .= $id; 
            $changed = TRUE;
          }
          if ($changed === TRUE) {
            // Save the changes for your objects here.
          }
          else {
            // Skip this step if something went wrong or changes weren't presented.
            throw new \Exception('Skip if cant handle');
          }
        }
        catch (\Throwable $th) {
          $this->messenger->addError($th->getMessage());
        }
      }
    
      /**
       * Finish operation.
       */
      public function finish($success, $results, $operations, $elapsed) {
        if ($success) {
          // Change success message here.
          $this->messenger->addMessage('Example batch is finished!');
        }
        else {
          $error_operation = reset($operations);
          $arguments = print_r($error_operation[1], TRUE);
          $message = "An error occurred while processing {$error_operation[0]} with arguments: {$arguments}";
          $this->messenger->addMessage($message, 'error');
        }
      }
    
    }

Important Notes:

  • Replace placeholders in code comments with your specific requirements.
  • Ensure proper file placement and adhere to Drupal coding standards.
  • This is a basic structure; refer to Drupal documentation for advanced functionalities.
  • Explore $context['sandbox'] and other features in official documentation: Batch API Overview.
Posted by Sujan Shrestha
Categorized:
PREVIOUS POST
banner