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.
*/
- 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
- 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'
- 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']
- 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);
}
}
- 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');
}
}
}
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)