DEV Community

mialdi98
mialdi98

Posted on • Edited on

4 1

How to create a clean OOP Batch on Drupal 9?

While we are waiting for Drupal 9 core implementation of OOP Batches we need a start point for them.

Example is for Drupal 9.2.6+ (PHP 7.4.2+)

Possible path to modules: web/modules/custom

  • Create module and name it module_example and add there /module_example/module_example.module. It's required file so just place it in module folder.

module_example.module

<?php
/*
 * Every module needs this file.
*/

Enter fullscreen mode Exit fullscreen mode
  • Add /module_example/module_example.info.yml file. Also required file, fill in info about module.

module_example.info.yml

# Change name, description, package on your needs.
name: Module Example
type: module
description: Module example
package: Module example
core_version_requirement: ^9

Enter fullscreen mode Exit fullscreen mode
  • Add /module_example/module_example.routing.yml file. Fill in here route for our form from where we will be starting our batch.

module_example.routing.yml

# Change here routing name,path,_form,_title for your specific form.
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'
Enter fullscreen mode Exit fullscreen mode
  • Add /module_example/module_example.services.yml file. We need it for our batch, it will be a service because we haven't any special definition for it.

module_example.services.yml

# Change here service name, class and arguments to inject for your needs.
services:
  module_example.batch_example:
    class: Drupal\module_example\Batch\BatchExample
    arguments: ['@messenger', '@extension.list.module']

Enter fullscreen mode Exit fullscreen mode
  • Create /module_example/src folder.
  • Create /module_example/src/Form folder.
  • Create /module_example/src/Form/ExampleForm.php file. We need this form to control our batch from somewhere. This example uses 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);
  }

}

Enter fullscreen mode Exit fullscreen mode
  • Create /module_example/src/Batch folder.
  • Add /module_example/src/Batch/BatchExample.php file.

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');
    }
  }

}

Enter fullscreen mode Exit fullscreen mode

It's only a skeleton for batch without full overview of it's functionality.
No $context['sandbox'] and else where used here but you can read about it in official documentation here - https://www.drupal.org/docs/7/api/batch-api/overview

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay