Как правильно удалить коллекцию полей?

9

Версия Drupal: 7.21
Версия модуля сбора данных: 7.x-1.0-beta5

Краткое объяснение : Я занят попытками импортировать коллекции полей программно, но при удалении некоторых из них всегда остается какая-то «поддельная» коллекция полей.

Подробное объяснение : у моих пользователей есть поле для сбора полей в их профиле. Эта коллекция полей содержит 3 текстовых поля. Я хочу импортировать данные из пользовательской базы данных SQL в поле коллекции пользователя. Эта коллекция полей может иметь несколько значений. Когда я импортирую данные в первый раз, все работает нормально, я вижу данные в полях коллекции полей. Отлично.

Но здесь начинается сложная часть. Допустим, я импортирую для одного конкретного пользователя 5 строк из пользовательской базы данных. Они добавляются в коллекцию полей, поэтому эта коллекция полей содержит 5 элементов, каждый из которых содержит 3 поля. Затем я удаляю несколько строк из своей пользовательской базы данных, чтобы у меня осталось только 3 строки для этого пользователя. Я снова запускаю импорт, обновляя первые 3 элемента коллекции полей, но затем у меня остаются 2 элемента из предыдущего импорта. Их следует удалить, потому что у меня есть только 3 импортированные строки, но все еще 5 элементов коллекции полей.

Поэтому я попытался удалить эти элементы коллекции полей, но всегда остается один или несколько элементов. Когда я смотрю на профиль пользователя, поля пусты, но что-то там еще есть. Допустим, в этот момент я добавил 5 новых строк для пользователя в своей пользовательской базе данных, поэтому у меня есть 8 строк для этого пользователя. Затем я снова запускаю импорт. Первые 3 элемента обновляются, но затем, когда я пытаюсь добавить 4-ую строку, он все еще получает идентификатор объекта из 4-го элемента коллекции полей, пытается обновить его, но не удается и возвращает эту ошибку:

 Fatal error: Call to undefined method stdClass::save()

Я попытался удалить элементы коллекции полей с помощью каждого из следующих методов:

// Method 1
entity_delete_multiple('field_collection_item', array($fc_id));

// Method 2
$field_collection_item = field_collection_item_load($fc_id);
$field_collection_item->delete();

// Method 3
$field_collection_item = field_collection_item_load($fc_id);
$field_collection_item->deleteRevision();

Это мой полный код:

function import_user_field_collection(&$user, $old_user_id) {

  // I do a query to get the rows I want to import for this specific user.
  db_set_active('custom_sql_database');
  $result = db_query("SELECT * FROM {users} WHERE user_id = :user_id", array(':user_id' => $old_user_id));

  db_set_active('default');
  $i = 0; // Keep count of how many rows I imported.
  foreach($result as $row) {
    // Check if the field collection item already exists.
    if(!empty($user->field_profile_diploma_opleiding[LANGUAGE_NONE][$i]['value'])) {
      // If it does exists, update this particular field collection item.
      $fc_id = $user->field_profile_diploma_opleiding[LANGUAGE_NONE][$i]['value'];
      $field_collection_item = entity_load('field_collection_item', array($fc_id));
      // These 3 text fields are children of the field collection field.
      $field_collection_item[$fc_id]->field_profile_diploma_instituut[LANGUAGE_NONE][0]['value'] = $row->instituut;
      $field_collection_item[$fc_id]->field_profile_diploma_vakgebied[LANGUAGE_NONE][0]['value'] = $row->vakgebied;
      $field_collection_item[$fc_id]->field_profile_diploma_jaar[LANGUAGE_NONE][0]['value'] = $row->jaar_diploma;
      $field_collection_item[$fc_id]->save(TRUE);
    } else {
      // If the field collection item doesn't exist I want to create a new field collection item.
      $field_collection_item = entity_create('field_collection_item', array('field_name' => 'field_profile_diploma_opleiding'));
      $field_collection_item->setHostEntity('user', $user);
      $field_collection_item->field_profile_diploma_instituut[LANGUAGE_NONE][0]['value'] = $row->instituut;
      $field_collection_item->field_profile_diploma_vakgebied[LANGUAGE_NONE][0]['value'] = $row->vakgebied;
      $field_collection_item->field_profile_diploma_jaar[LANGUAGE_NONE][0]['value'] = $row->jaar_diploma;
      $field_collection_item->save(TRUE);
    }
    $i++;
  }

  $fc_fields = field_get_items('user', $user, 'field_profile_diploma_opleiding');

  // Check if there are more field collection items than imported rows
  if(count($fc_fields) > $i) {
    for($i; $i <= count($fc_fields); $i++) {
      // Run through each field collection item that's left from the previous import and delete it.
      if(!empty($user->field_profile_diploma_opleiding[LANGUAGE_NONE][$i]['value'])) {
        // Method 1
        $fc_id = $user->field_profile_diploma_opleiding[LANGUAGE_NONE][$i]['value'];
        entity_delete_multiple('field_collection_item', array($fc_id));

        // Method 2
        //$field_collection_item = field_collection_item_load($fc_id);
        //$field_collection_item->delete();

        // Method 3
        //$field_collection_item = field_collection_item_load($fc_id);
        //$field_collection_item->deleteRevision();
      }
    }
  }
}

Итак, мой вопрос: как удалить элементы коллекции полей, чтобы они на самом деле исчезли?

СМО
источник
2
entity_delete_multipleна 100% определенно правильный способ сделать это - взгляните на field_collection_field_deleteфункцию, которая сама Field Collection использует для очистки элементов при удалении указанного поля
Клайв
Большое спасибо за ваш ответ, я ценю это. Вы случайно не знаете, какие аргументы я должен предоставить с field_collection_field_delete? Я вижу, что подпись - field_collection_field_delete ($ entity_type, $ entity, $ field, $ instance, $ langcode, & $ items), но я действительно не знаю, какие значения вставить в: $ entity (это пользователь или коллекция полей) ?), $ field (возвращаемое значение из field_collection_item_load?), $ instance, $ langcode (und?) и $ items.
Смос
1
Эта конкретная функция является подключаемой реализацией, в основном, когда любое поле удаляется, имя поля передается этой функции, и Field Collection проверяет, существует ли объект FC, связанный с этим экземпляром поля. Если есть, он удаляет его, используя entity_delete_multiple(). Вам может потребоваться запустить cron пару раз после удаления полей (данные полей очищаются по расписанию, чтобы не загружать загрузку одной страницы всей этой обработкой)
Clive
Я снова попытался использовать entity_delete_multiple и заметил, что элементы удаляются в таблице field_collection_item, но поля все еще существуют в таблице field_data_field_collection_name. Я думаю, что это приводит к фатальной ошибке Вызов неопределенного метода stdClass :: save (), потому что они должны быть полями, но с ними не связан элемент коллекции полей. Когда я использую $ field_collection_item-> deleteRevision, он удаляет данные в обеих таблицах, но когда я сохраняю пользователя, в таблицу добавляется строка field_data_field_collection_name, и это пустой элемент коллекции полей.
Смос
@Smos: эй, приятель, можешь мне помочь с подобной проблемой ( drupal.stackexchange.com/questions/239784/… )? Я попробовал соответствующие части вашего кода, но не смог заставить его работать.
Сиско

Ответы:

13

Я столкнулся с похожим случаем использования, когда я хотел отобразить некоторые данные в коллекцию полей во время hook_feeds_presave (), так как структура источника была слишком сложной для Feeds. Я обнаружил, что entity_delete_multiple () удалил элементы коллекции полей, но когда я отредактировал узел, там все еще была куча пустых коллекций полей. Удаление и удаление сделали свое дело, которое я нашел здесь: https://drupal.stackexchange.com/a/31820/2762

Если источник каналов изменился, я удаляю все элементы коллекции полей и воссоздаю. Надеюсь, это полезно.

foreach ($node->field_international_activity[LANGUAGE_NONE] as $key => $value) {
  // Build array of field collection values.
  $field_collection_item_values[] = $value['value'];

  // Unset them.  
  unset($node->field_international_activity[LANGUAGE_NONE][$key]);
}

// Delete field collection items.
entity_delete_multiple('field_collection_item', $field_collection_item_values);
Винсент
источник
Этот двухэтапный подход является единственным способом, который работает AFAIK. Не забудьте на node_save($node)свой узел.
Бернхард Фюрст
@ BernhardFürst на самом деле нам не нужно node_save($node), DrupalEntityControllerбудем делать эту работу
coffeduong
8

Лучший способ сделать это сейчас - это позвонить, $field_collection->delete()и это все обработает.

 <?php
    /**
     * Deletes the field collection item and the reference in the host entity.
     */
    public function delete() {
      parent::delete();
      $this->deleteHostEntityReference();
    }

    /**
     * Deletes the host entity's reference of the field collection item.
     */
    protected function deleteHostEntityReference() {
      $delta = $this->delta();
      if ($this->item_id && isset($delta)) {
        unset($this->hostEntity->{$this->field_name}[$this->langcode][$delta]);
        entity_save($this->hostEntityType, $this->hostEntity);
      }
    }
 ?>
Бенджи
источник
0

Приведенные выше ответы не являются лучшим способом, при этом все остальные элементы, исчезнувшие из коллекции полей, были сброшены, а другой способ - с ->delete()ошибкой в ​​модуле Entity.

Правильный путь. Хорошо, что я сделал, было это:

В моем случае я хотел удалить последний элемент в коллекции полей

взгляните на этот код (для начинающих: «помните, что у вас уже должна быть загружена $ entity»)

// Remove the field value
unset($entity->field_salida_mercanc_a[LANGUAGE_NONE][count($entity->field_salida_mercanc_a[LANGUAGE_NONE])-1]);

// Reset the array to zero-based sequential keys
$entity->field_salida_mercanc_a[LANGUAGE_NONE] = array_values($entity->field_salida_mercanc_a[LANGUAGE_NONE]);

// Save the entity
entity_save($entity_type, $entity);

это все! другой способ

//Get the node wapper
$wrapper = entity_metadata_wrapper($entity_type, $entity);
//Get the last position of the array items, thanks to the ->value() statement you can get the complete Array properties.
$field_collection_item_value = $wrapper->field_collection_mine[count($wrapper->field_collection_mine->value())-1]->value();
//Do not use 'unset' to delete the item due to is not the correct way, I use the entity_delete_multiple provided by the entity API
entity_delete_multiple('field_collection_item', array($field_collection_item_value->item_id));

Описанный выше способ хорош при использовании entity_metadata_wrapperфункции, но в этом случае есть сложная ошибка, которую я не знаю, как ее решить, вы можете проверить ее на https://drupal.org/node/1880312 и после применения патча в # 9 Вы получите следующую проблему, проверьте ее здесь https://drupal.org/node/2186689 Эта ошибка также, если вы используете ->delete()функцию.

Надеюсь, это кому-нибудь поможет.

svelandiag
источник
0

использование vbo для удаления элементов коллекции полей. он автоматически удалит отношение поля с сущностью узла элемента коллекции полей.

Терри Чжан
источник
0

У меня была похожая проблема, когда я импортирую данные из канала в элемент FC. Когда из канала выполняется обновление сущности хоста, и я импортирую эти изменения, я хотел убедиться, что все существующие элементы FC, которых больше не существует из источника канала, были удалены.

Мое решение:

// Load host entity that I'm updating.
$host_entity = node_load($nid);
$host_entity_wrapper = entity_metadata_wrapper('node', $host_entity);

// Clear out the references to the existing FC items so that I have a fresh start.
$host_entity_wrapper->field_my_fc_items->set(NULL);

// Re-create the FC items from the source feed data.
foreach ($feed_data->items as $feed_item) {
  $fc = entity_create('field_collection_item', array('field_name' => 'field_my_fc_items'));
  $fc->setHostEntity($host_entity);
  // Some some fields on the FC item from the feed data.
  $fc_wrapper = entity_metadata_wrapper('field_collection_item', $fc);
  $fc_wrapper->field_some_fc_item_field->set($feed_item->data);
}

// Sync other field info onto host entity.
...

$host_entity_wrapper->save();

И это все. Метод hook_field_update ( field_collection_field_update) Field Collection позаботится о фактическом удалении любых существующих элементов FC, на которые была дана обратная ссылка.

Единственным недостатком является то, что если в данных FC не было никаких изменений, они все равно удаляются и воссоздаются. Но это не имеет большого значения для меня.

Брайан
источник