DEV Community

mialdi98
mialdi98

Posted on • Edited on

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

Top comments (0)