При разработке проектов, сложнее чем сайт-визитка, нередко возникает необходимость в обработке больших объёмов данных. Сплошь и рядом заказчики хотят интеграцию с 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