Здравствуйте!
Второй день бьюсь над решением проблемы. Судя по всему, мне не хватает понимания php и drupal, поэтому приходится обращаться к сообществу.
Передо мной стоит задача залить n новых нод в друпал. Я использую для этого node_save и пишу вот такой код, который загружаю через php-блок в Devel:
<?php
// Задаю массив массивов с содержимым новых нод
$nodes_for_import = array(
array("1","5048","A","4838","4655","1959-08-22:T00:00:00","34000","2","3"),
array("2","5041","H","112","4655","1959-08-26:T00:00:00","33071","4","2"),
);
// Прогоняю массив через цикл, создавая из каждого подмассива новую ноду
foreach ($nodes_for_import as $node_fi) {
$node = new StdClass();
$node->nid = 0;
$node->type = 'my_type';
$node->language = 'ru';
$node->comment = '2';
$node->status = 1;
$node->field_0 = array(array("value" => $node_fi[0]));
$node->field_1 = array(array("nid" => $node_fi[1]));
$node->field_2 = array(array("value" => $node_fi[2]));
$node->field_3 = array(array("nid" => $node_fi[3]));
$node->field_4 = array(array("nid" => $node_fi[4]));
$node->field_5 = array(array("value" => $node_fi[5]));
$node->field_6 = array(array("value" => $node_fi[6]));
$node->field_7 = array(array("value" => $node_fi[7]));
$node->field_8 = array(array("value" => $node_fi[8]));
node_submit($node);
node_save($node);
}
?>
Всё работает. Проблемы начинаются тогда, когда я пытаюсь создать несколько тысяч таких нод на тестовом домене — скрипт вылетает из-за нехватки памяти (я на хорошем VPS, ограничения все установлены как можно «лояльнее»). Если скармливать примерно по пятьдесят-шестьдесят нод, скрипт работает, но, если больше, начинает вылетать.
Как я понял, решением проблемы может стать Batch API, но у меня не получается подружить мой код с ним, причём я, в силу недостаточности своих знаний и относительно небольшого опыта, не могу определить, что я делаю не так. Разумеется, я читал API, искал в Сети всё, что касается работы с Batch API и просмотрел примеры того, что описывают друпалеры в своих блогах. Результат — нулевой -(
Вот, как выглядит мой неработающий код в последнем варианте, скорректированном с учётом того, что я почерпнул в других местах. Я привожу его со своими комментариями — как я понимаю происходящее:
<?php
// Задаю массив массивов с содержимым новых нод
$nodes_for_import = array(
array("1","5048","A","4838","4655","1959-08-22:T00:00:00","34000","2","3"),
array("2","5041","H","112","4655","1959-08-26:T00:00:00","33071","4","2"),
);
// Создаю batch, в качестве операции указываю функцию my_imp, которой в качестве аргумента подаётся мой массив с данными
$batch = array(
'title' => t('Exporting'),
'operations' => array(
array('my_imp', array($nodes_for_import)),
),
'finished' => 'my_imp_finished',
'init_message' => t('Starting...'),
'progress_message' => t('Batch current out of total'),
'error_message' => t('An error occurred and some or all of the batch has failed.'),
);
batch_set($batch);
// требуется, если операции проходят вне 'submit' обработчика форм (да-да, скопировано из API)
batch_process();
// Функция операции
function my_imp($nodes_for_import, &$context) {
// «Порция». Для примера я попросил, чтобы данные загонялись по два за раз
$limit = 2;
// Определяем, чем будет руководствоваться скрипт при создании прогресс-бара
if (empty($context['sandbox']['progress'])) {
$context['sandbox']['progress'] = 0;
// Устанавливаем максимальное значение прегресс-бара, равное количеству подмассивов в массиве с данными
$context['sandbox']['max'] = count($nodes_for_import);
}
// Задаём, что является массивом данных для загрузки
if(empty($context['sandbox']['items'])) {
$content['sandbox']['items'] = $nodes_for_import;
}
// Счётчик, чтобы отсчитывать количество элементов в «порции»
$counter = 0;
if(!empty($context['sandbox']['items'])) {
// Если это не первый шаг, достаём следующую порцию данных
if ($context['sandbox']['progress'] != 0) {
array_splice($context['sandbox']['items'], 0, $limit);
}
// Достаём по очереди каждый элемент массива и называем его $item
foreach ($content['sandbox']['items'] as $item) {
if ($counter != $limit) {
// Проделываем с каждым массивом данных то же, что проделывали в оригинальном коде
$node = new StdClass();
$node->nid = 0;
$node->type = 'game';
$node->language = 'ru';
$node->comment = '2';
$node->status = 1;
$node->field_0 = array(array("value" => $item[0]));
$node->field_1 = array(array("nid" => $item[1]));
$node->field_2 = array(array("value" => $item[2]));
$node->field_3 = array(array("nid" => $item[3]));
$node->field_4 = array(array("nid" => $item[4]));
$node->field_5 = array(array("value" => $item[5]));
$node->field_6 = array(array("value" => $item[6]));
$node->field_7 = array(array("value" => $item[7]));
$node->field_8 = array(array("value" => $item[8]));
node_submit($node);
node_save($node);
// увеличиваем счётчик пока он лимита не достигнет
$counter++;
// Кажется, этот if должен закрываться здесь, хотя, может быть и нет
}
// Счётчик прогресса увеличиваем
$context['sandbox']['progress']++;
// Выводим сообщение о том, какая по счёту нода у нас в «производстве»
$context['message'] = t('Now processing node %node of %count', array('%node' => $context['sandbox']['progress'], '%count' => $context['sandbox']['max']));
// М-м-м?
$context['results']['nodes'] = $context['sandbox']['progress'];
// Закрываем луп
}
// Операция, которую мы хотели проделать за один проход, завершена (?)
}
// Проверка того, всё ли сделано.
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
}
}
// Функция, запускаемая по завершении
function my_imp_finished($success, $results, $operations) {
if ($success) {
// Here we do something meaningful with the results.
$message = t('%nodes nodes processed', array('%nodes' => $results['nodes']));
}
else {
// An error occurred.
// $operations contains the operations that remained unprocessed.
$error_operation = reset($operations);
$message = t('An error occurred while processing %error_operation with arguments: arguments',
array('%error_operation' => $error_operation[0], 'arguments' => print_r($error_operation[1], TRUE)));
}
drupal_set_message($message);
}?>
Код рабочий, но только в том смысле, что ошибок не выдаёт — когда я пытаюсь с его помощью загрузить ноды, всё его действие сводится только к тому, чтобы показать мне слово «Exporting» и прогресс бар, который быстро проходит с 0 до 1 (его максимальное значение) и загружает мне ту страницу, с которой я запускал сниппет. Я уже сломал голову, но так и не смог понять, почему это происходит. Помогите, пожалуйста.
Комментарии
foreach ($content['sandbox']['items'] as $item) {
замените на
foreach ($context['sandbox']['items'] as $item) {
Спасибо! Опечатка глупая, и я её проглядел как-то. Там чуть выше есть ещё такая же. Проблема в том, что ничего не изменилось в результате — same shit, как говорится
Где Вы вызываете batch?
Возможно Вы вставляете этот код не в модуль, а в файл, который подключается не на всех страницах, например your_module.batch.inc, в таком случае Вам надо добавить в определение batch строчку для включения файла.
<?php
$batch = array(
'title' => t('Exporting'),
'operations' => array(
array('my_imp', array($nodes_for_import)),
),
'file' => 'path/to/your_module/your_module.batch.inc', //вот эта строчка
'finished' => 'my_imp_finished',
'init_message' => t('Starting...'),
'progress_message' => t('Batch current out of total'),
'error_message' => t('An error occurred and some or all of the batch has failed.'),
);
?>
Я вызываю его, вставляя код в блок Execute PHP-code, поставляемый с модулем Devel. Я думал, что, раз у меня строка Exporting и прогресс-бар появляются, то вызов batch всё же происходит, но почему-то не доходит дело собственно до обработки массива с данными.
Именно! Не доходит дело, потому что после вызова batch Вашего кода просто нет, поместите его в модуль.
Попробуйте вставить batch в модуль, и вызывать его оттуда. Например на этапе отладки через хук меню, а потом - в зависимости от потребностей, например к крону можно привязать.
Большое спасибо за помощь! Благодаря вам всё разрешилось. В итоге я поступил следующим образом.
1. Создал свой модуль, в рамках которого у меня есть инклюд mymodule.batch.inc, содержащий две функции — mymodule_imp и mymodule_imp_finished.
2. Вызвал из Devel batch следующим образом:
<?php
// Задаю массив массивов с содержимым новых нод
$nodes_for_import = array(
array("1","5048","A","4838","4655","1959-08-22:T00:00:00","34000","2","3"),
array("2","5041","H","112","4655","1959-08-26:T00:00:00","33071","4","2"),
); // Создаю batch, в качестве операции указываю функцию my_imp, которой в качестве аргумента подаётся мой массив с данными
$batch = array(
'title' => t('Exporting'),
'operations' => array(),
'file' => 'sites/all/modules/mymodule/mymodule.batch.inc', //Строчка, указывающая на инклюд
'finished' => 'mymodule_imp_finished',
'init_message' => t('Starting...'),
'progress_message' => t('Batch current out of total'),
'error_message' => t('An error occurred and some or all of the batch has failed.'),
); $batch['operations'][] = array ('mymodule_imp', array($nodes_for_import)); batch_set($batch);
// требуется, если операции проходят вне 'submit' обработчика форм (да-да, скопировано из API)
batch_process();?>
3. PROFIT!
В итоге всё работает. Надеюсь, этот топик поможет тем, кто столкнётся с подобной проблемой после меня
В идеале надо написать
<?php
'file' => drupal_get_path('module', 'mymodule') . '/mymodule.batch.inc',
?>
потому что модуль может и в другой директории лежать.
Да, это понятно, но здесь мне было чётко известно, куда я его положил
с BATCH API разобрался спасибо за топик, но вот некоторая тормознутость появилась: для того чтобы каждый раз при перезагруки сайта (batch api) надо снова бежать к нужной строчке в csv-файле, может сталкивались? Вот тут тему составил с вопросом: http://www.drupal.ru/node/61800
Не здесь. Я лишний час потратил пытаясь понять в чём пробелема Закрываться if должен после
<?php $context['results']['nodes'] = $context['sandbox']['progress']; ?>