“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:
Create Module:
- Location: web/modules/custom
- Folder: module_example
- File: /module_example/module_example.module
<?php /* * @file */Module Info:
- File: /module_example/module_example.info.yml
name: Module Example type: module description: Module example package: Module example core_version_requirement: ^9Routing:
- 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'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']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); } }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.
