Метод сущности загружается дважды

В последнее время я создавал модуль Drupal 7 с Entity API, но столкнулся с проблемой. При отладке формы редактирования я заметил, что метод загрузки вызывается дважды, что вызывает ошибку.

Устранимая фатальная ошибка: объект класса stdClass не может быть преобразован в строку в DatabaseStatementBase->execute() (regel 2039 van /drupal7/includes/database/database.inc).

Это вызвано тем, что FooController::load выполняется дважды.

Ниже приведен код, который я использовал.

function foo_menu() {
  $items = array();
  ...
  $items['admin/foo/%foo/edit'] = array(
    'title' => 'Edit foo',
    'page callback' => 'foo_edit',
    'page arguments' => array(2),
    'access arguments' => array('administer content'),
    'type' => MENU_CALLBACK,
  );
  ...
  return $items;
}

function foo_edit($foo) {
  return drupal_get_form('foo_edit_form', $foo);
}

function foo_edit_form($form, &$form_state, $foo) {
  $form['#foo'] = $foo;
  $form_state['values'] = $foo;

  $form['id'] = array(
    '#type' => 'hidden',
    '#value' => $foo->id,
  );
  ...
  $form['picture'] = array(
    '#type' => 'file',
    '#title' => t('Picture'),
    '#default_value' => $foo->picture->filename,
  );
  ...
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );

  return $form;
}

function foo_edit_form_submit($form, &$form_state) {
  $foo = (object)$form_state['values'];

  if ($picture = file_save_upload('picture')) {
    $picture = file_move($picture, FOO_FILE_PATH . $picture->filename, FILE_EXISTS_RENAME);
    $picture->status |= FILE_STATUS_PERMANENT;
    $picture = file_save($picture);      
    $foo->picture = $picture->fid;
  }

  entity_get_controller('foo')->save($foo);

  drupal_set_message(t('Foo saved.'));
  $form_state['redirect'] = 'admin/foo';
}

И, пожалуй, самая важная часть:

function foo_load ($id) {
  $foo = foo_load_multiple(array($id));
  return $foo ? $foo[$id] : FALSE;
}
function foo_load_multiple ($ids = array(), $conditions = array()) {
  return entity_load('foo', $ids, $conditions);
}
class FooController extends DrupalDefaultEntityController {
  ...     
  function load ($ids = array(), $conditions = array()) {
    $foos = parent::load($ids, $conditions);

    // Code executed twice
    error_log('FooController::load');

    foreach ($foos as $foo) {
      $foo->picture = file_load($foo->picture);
    }
    return $foos;
  }
  ...
}

Изменить: стек вызовов

... (Array, 11 elements)  
    11: load (Array, 7 elements)  
    10: entity_load (Array, 4 elements)  
    9: foo_load_multiple (Array, 4 elements)  
    8: foo_load (Array, 4 elements)  
    7: _menu_load_objects (Array, 4 elements)  
    6: _menu_translate (Array, 4 elements)  
    5: menu_get_item (Array, 4 elements)  
    4: menu_get_custom_theme (Array, 4 elements)  
    3: menu_set_custom_theme (Array, 4 elements)  
    2: _drupal_bootstrap_full (Array, 4 elements)  
    1: drupal_bootstrap (Array, 4 elements)  
Called from /sites/bar/modules/custom/foo/foo.module, line 367  

... (Array, 20 elements)  
    20: load (Array, 7 elements)  
    19: entity_load (Array, 4 elements)  
    18: foo_load_multiple (Array, 4 elements)  
    17: foo_load (Array, 4 elements)  
    16: _menu_load_objects (Array, 4 elements)  
    15: _menu_translate (Array, 4 elements)  
    14: menu_local_tasks (Array, 4 elements)  
    13: menu_tab_root_path (Array, 4 elements)  
    12: menu_get_active_help (Array, 4 elements)  
    11: system_block_view (Array, 2 elements)  
    10: call_user_func_array (Array, 4 elements)  
    9: module_invoke (Array, 4 elements)  
    8: _block_render_blocks (Array, 4 elements)  
    7: block_list (Array, 4 elements)  
    6: block_get_blocks_by_region (Array, 4 elements)  
    5: block_page_build (Array, 4 elements)  
    4: drupal_render_page (Array, 4 elements)  
    3: drupal_deliver_html_page (Array, 4 elements)  
    2: drupal_deliver_page (Array, 4 elements)  
    1: menu_execute_active_handler (Array, 4 elements)  
Called from /sites/bar/modules/custom/foo/foo.module, line 367  

Кто-нибудь сталкивался с этой проблемой раньше?

Заранее спасибо.

Решение (спасибо @Berdir):

Вместо переопределения DrupalDefaultEntityController::load() с помощью attachLoad():

protected function attachLoad(&$foos, $revision_id = FALSE) {
    foreach ($foos as $foo) {
      $foo->picture = file_load($foo->picture);
    }
    parent::attachLoad($foos, $revision_id);
  }

person Bart    schedule 07.03.2011    source источник


Ответы (1)


Мне кажется, что второй вызов на самом деле передает что-то недопустимое (объект вместо int/string).

Вместо этого вызова error_log() попробуйте "debug_print_backtrace()" или, если у вас установлен devel.module, "ddebug_backtrace()". Это даст вам стек вызовов, чтобы выяснить, кто именно вызывает вашу функцию загрузки, когда. Обратите внимание, что первая команда напечатает много необработанного текста, вторую намного легче читать.

Редактировать: вместо переопределения метода load() следует реализовать attachLoad(). Это будет вызываться только один раз для вновь загруженных объектов. См. http://api.worldempire.ch/api/privatemsg/privatemsg.module/function/PrivatemsgMessageController::attachLoad/7-1 для примера.

person Berdir    schedule 07.03.2011
comment
Я думаю, что это поведение по умолчанию, с которым мне уже приходилось бороться. Объекты меню загружаются один раз для меню и один раз для получения справочной системы. Однако объекты в любом случае должны быть статически кэшированы, и должен поддерживаться многократный вызов foo_load(). Итак, проблема в вашей строке file_load(). Если он уже загружен, это то, что вы получите. Я обновлю свой ответ, указав, как это исправить. - person Berdir; 09.03.2011
comment
Я использую quickfix в данный момент, но, очевидно, я бы предпочел не использовать его. File_load(string) вызывает ошибку при второй загрузке(), потому что он пытается загрузить объект, который уже вставлен в первую загрузку(). Кажется странным, что при второй загрузке() объект изображения уже вставлен в объект foo. Вы могли бы подумать, что он будет выбран из базы данных в parent::load, но каким-то образом он использует объект, загруженный в первую загрузку () (кэширование?). Не помогает и отсутствие документации по API сущности :). - person Bart; 09.03.2011
comment
См. мое редактирование выше, вы хотите использовать attachLoad() вместо переопределения load(). - person Berdir; 09.03.2011