С помощью Batch API можно разбивать длительные операции на части и выполнять каждую часть в отдельном http запросе. Такой механизм позволяет избежать ограничение в 30 секунд (по умолчанию) на выполнение php скриптов.
С работой Batch API сталкивались все, например эта штука используется при установке друпала или обновлении переводов:
Простейший пример работы с Batch API, в котором мы изменим дату создания всех нод на текущую:
function mymodule_batch_form($form, &$form_state) {
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Начать',
);
return $form;
}
function mymodule_batch_form_submit($form, &$form_state) {
// Подготавливаем данные для операций
$result = db_select('node', 'n')->fields('n', array('nid'))->execute();
// Создаём массив с операциями.
// Каждая операция будет выполнена в отдельном http запросе.
foreach ($result as $row) {
$operations[] = array('mymodule_change_date', array($row->nid));
}
$batch = array(
// Массив операций и их параметров
'operations' => $operations,
// Функция, которая будет выполнена после окончания всех операций
'finished' => 'mymodule_batch_finished',
// Заголовок страницы с прогресс баром.
// Опционально, по умолчанию t('Processing')
'title' => 'Обновление дат',
// Сообщение, показываемое при инициализации.
// Опционально, по умолчанию t('Initializing.')
'init_message' => 'Подготовка данных',
// Сообщение, показывемое при выполнении операций.
// Опционально, по умолчанию t('Completed @current of @total.')
'progress_message' => 'Выполнено @current из @total.',
// Сообщение показываемое при ошибке выполнения операции.
// Опционально, по умолчанию t('An error has occurred.')
'error_message' => 'Произошла ошибка.',
);
batch_set($batch);
// Если Batch API используется не из _submit функции,
// то дополнительно нужно вызвать batch_process();
}
function mymodule_change_date($nid, &$context) {
// Производим манипуляции над нодами
$node = node_load($nid);
$node->created = time();
node_save($node);
// Эта информация будет доступна в mymodule_batch_finished
$context['results'][] = $node->title;
// Сообщение выводимое под прогресс баром после окончания текущей операции
$context['message'] = 'Обновлена дата у материала <em>' . $node->title . '</em>';
}
function mymodule_batch_finished($success, $results, $operations) {
if ($success) {
drupal_set_message('Обновлена дата у ' . count($results) . ' материалов:' . theme('item_list', array('items' => $results)));
}
else {
drupal_set_message('Завершено с ошибками.', 'error');
}
}
Операции в свою очередь можно так же разбивать на части. Например можно сначала обновить заголовки нод, а потом выслать уведомления их авторам (хотя никто не мешает сделать это в одной операции, но для примера пусть будет так):
function mymodule_batch_form_submit($form, &$form_state) {
$batch = array(
'operations' => array(
array('mymodule_change_date', array()),
array('mymodule_send_notify', array()),
),
'finished' => 'mymodule_batch_finished',
);
batch_set($batch);
}
function mymodule_change_date(&$context) {
// Переменная $context['sandbox'] используется для хранения данных между итерациями
if (empty($context['sandbox'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['prev_nid'] = 0;
$context['sandbox']['max'] = db_select('node')->countQuery()->execute()->fetchField();
}
$nid = db_select('node', 'n')
->fields('n', array('nid'))
->condition('nid', $context['sandbox']['prev_nid'], '>')
->execute()
->fetchField();
$node = node_load($nid);
$node->created = time();
node_save($node);
$context['sandbox']['progress']++;
$context['sandbox']['prev_nid'] = $nid;
$context['results'][] = $node->title;
$context['message'] = 'Обновлена дата у материала <em>' . $node->title . '</em>';
// Если операция не завершена, то показываем на каком она этапе
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
}
}
function mymodule_send_notify(&$context) {
// Код по аналогии
}
Написанное актуально для
Drupal 7