Batch API и node_save [РЕШЕНО]

Главные вкладки

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 25 сентября 2010 в 3:33

Здравствуйте!

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

Комментарии

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 26 сентября 2010 в 4:01

"gorr" wrote:
foreach ($content['sandbox']['items'] as $item) {
замените на
foreach ($context['sandbox']['items'] as $item) {

Спасибо! Опечатка глупая, и я её проглядел как-то. Там чуть выше есть ещё такая же. Проблема в том, что ничего не изменилось в результате — same shit, как говорится Sad

Аватар пользователя gorr gorr 27 сентября 2010 в 13:40

Где Вы вызываете 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.'),
);

?>

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 27 сентября 2010 в 14:46

Я вызываю его, вставляя код в блок Execute PHP-code, поставляемый с модулем Devel. Я думал, что, раз у меня строка Exporting и прогресс-бар появляются, то вызов batch всё же происходит, но почему-то не доходит дело собственно до обработки массива с данными.

Аватар пользователя gorr gorr 27 сентября 2010 в 15:35

Именно! Не доходит дело, потому что после вызова batch Вашего кода просто нет, поместите его в модуль.

Аватар пользователя Sun-fire Sun-fire 27 сентября 2010 в 15:37

Попробуйте вставить batch в модуль, и вызывать его оттуда. Например на этапе отладки через хук меню, а потом - в зависимости от потребностей, например к крону можно привязать.

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 27 сентября 2010 в 23:55

Большое спасибо за помощь! Благодаря вам всё разрешилось. В итоге я поступил следующим образом.

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!

В итоге всё работает. Надеюсь, этот топик поможет тем, кто столкнётся с подобной проблемой после меня Smile

Аватар пользователя gorr gorr 28 сентября 2010 в 9:55

В идеале надо написать
<?php
'file' => drupal_get_path('module', 'mymodule') . '/mymodule.batch.inc',
?>
потому что модуль может и в другой директории лежать.

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 28 сентября 2010 в 14:28

"gorr" wrote:
потому что модуль может и в другой директории лежать.

Да, это понятно, но здесь мне было чётко известно, куда я его положил Smile

Аватар пользователя chel chel 5 мая 2011 в 14:48

с BATCH API разобрался спасибо за топик, но вот некоторая тормознутость появилась: для того чтобы каждый раз при перезагруки сайта (batch api) надо снова бежать к нужной строчке в csv-файле, может сталкивались? Вот тут тему составил с вопросом: http://www.drupal.ru/node/61800

Аватар пользователя SplasH SplasH 4 июля 2011 в 8:46

<a href="mailto:ingumsky@drupal.org">ingumsky@drupal.org</a> wrote:
Кажется, этот if должен закрываться здесь, хотя, может быть и нет

Не здесь. Я лишний час потратил пытаясь понять в чём пробелема Smile Закрываться if должен после
<?php $context['results']['nodes'] = $context['sandbox']['progress']; ?>