Пакетные операции, как это делается в Drupal

в 20:22, , рубрики: batch, drupal, php, Веб-разработка, метки: , ,

При разработке проектов, сложнее чем сайт-визитка, нередко возникает необходимость в обработке больших объёмов данных. Сплошь и рядом заказчики хотят интеграцию с 1С, импорт существующих прайсов, выгрузку на Яндекс-Маркет, миграцию откуда угодно. Очевидно, что создать тысячу нод за один запуск не выйдет, а раз так, то на помощь приходят пакетные операции aka Batch operations.
Начнём сначала.

Создание модуля

example/example.info

name = example
description = "example batch"
core = 7.x
version = "7.x-0.1"

example/example.module

<?php
function example_menu() {
  $items["example"] = array(
    "title" => "Example",
    "type" => MENU_NORMAL_ITEM,
    "page callback" => "drupal_get_form", // функция, которая будет вызвана для формирования этой страницы
    "page arguments" => array('example_form'), // аргументы этой функции
    'access arguments' => array('administer site configuration'),
  );
  return $items;
}

// а это та самая функция, которая генерирует форму example_form
function example_form($form, &$form_state) {
  $form["submit"] = array(
    '#type' => 'submit',
    '#value' => t('Submit')
  );
  $form["#action"] = "example";
  return $form;
}

Вот мы имеем элементарный модуль, который регистрирует в системе пункт меню, перейдя по которому, мы увидим форму с кнопкой. Пункт этот доступен только пользователям с правом конфигурирования сайта, то есть, кому попало ни ссылку, ни тем более страницу, Drupal даже не покажет.

Обработка формы и регистрация пакетной операции

example/example.module

//а эта функция будет вызвана для обработки нашей однокнопочной формы
function example_form_submit() {
  $batch = array(
    'title' => t('import'),
    'operations' => array(
        array('example_batch_0', array($_SERVER['REQUEST_TIME'])),
//      array('example_batch_1', array($_SERVER['REQUEST_TIME'])),
//      ... столько, сколько надо
    ),
    'finished' => 'example_batch_finished',
  );
  batch_set($batch);//регистрируем
  batch_process($batch);//и инициируем работу пакетной операции
}
Выполнение пакетной операции

example/example.module

// особое внимание на порядок аргументов, он именно такой, сначала аргументы, потом &$context
function example_batch_0($time, &$context) {
  if (empty($context['sandbox'])) { // песочница пуста, example_batch_0 была вызвана впервые
    $context['sandbox']['progress'] = 0;
    $context['sandbox']['max'] = 100500; // или столько, сколько надо
  }

// делаем что-то
// возможно, пишем что-то в $context["result"]

  $context['message'] = $context['sandbox']['progress']. "/" . $context['sandbox']['max'] . ($_SERVER['REQUEST_TIME'] - $time) . " sec";
  $context['sandbox']['progress']++;
  if ($context['sandbox']['progress'] < $context['sandbox']['max']) {
    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
  }
}

$context['sandbox'] — наша песочница, часть данных, которая принадлежит только этой функции из пакета,
когда(если) дело дойдёт до example_batch_1, песочница снова будет чистой, это позволяет нам узнать, на каком шаге текущей операции мы находимся

$context['result'] — часть данных, которая доступна всему пакету последовательно

$context['message'] — строка, которая будет послана браузеру, в нашем случае видно затраченное время и общий прогресс текущей операции

$context['finished'] — это значение позволяет системе понять, завершена текущая операция, 1 или отсутствие результата означает, что здесь уже всё сделано

Завершение пакетной операции

function example_batch_finished($success, $results, $operations) {
  if ($success)  drupal_set_message('yep');
  else drupal_set_message('nope', "error");
}

$success — означает, что пакет завершён без ошибок

$results — наши результаты

$operations — наши операции, ведь один и тот же финиш может быть использован для разных пакетов

Напоследок

Не передавайте большие структуры данных в качестве аргументов, не храните их в песочнице и не выводите в результат. Drupal умеет склеивать операции в рамках одного запуска, но между запусками они будут храниться в базе данных. Большой объём данных в базу просто не передастся.

О том, где можно хранить промежуточные данные, в следующий раз.

Автор: Punk_UnDeaD


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js