Jak poprawnie usunąć kolekcję pól?

9

Wersja Drupal: 7.21
Wersja modułu kolekcji terenowej: 7.x-1.0-beta5

Krótkie wyjaśnienie : jestem zajęty próbą programowego importowania zbiorów pól, ale podczas usuwania niektórych z nich zawsze pozostaje trochę „fałszywych” zbiorów pól.

Długie wyjaśnienie : Moi użytkownicy mają w swoim profilu pole zbioru pól. Ta kolekcja pól zawiera 3 pola tekstowe. Chcę zaimportować dane z niestandardowej bazy danych SQL do kolekcji użytkownika. Ta kolekcja pól może mieć wiele wartości. Kiedy importuję dane po raz pierwszy, wszystko działa dobrze, widzę dane w polach kolekcji pól. Świetny.

Ale nadchodzi trudna część. Załóżmy, że importuję dla jednego konkretnego użytkownika 5 wierszy z niestandardowej bazy danych. Są one dodawane do kolekcji pól, więc ta kolekcja pól zawiera 5 elementów, z których każdy zawiera 3 pola. Następnie usuwam niektóre wiersze z mojej niestandardowej bazy danych, aby dla tego użytkownika pozostały tylko 3 wiersze. Ponownie uruchamiam import, aktualizując pierwsze 3 elementy kolekcji pól, ale potem pozostały mi 2 elementy z poprzedniego importu. Powinny zostać usunięte, ponieważ mam tylko 3 zaimportowane wiersze, ale nadal 5 elementów kolekcji pól.

Próbowałem więc usunąć te elementy kolekcji pól, ale zawsze pozostaje jeden lub więcej elementów. Pola są puste, gdy patrzę na profil użytkownika, ale wciąż coś tam jest. Powiedzmy, że w tym momencie dodałem 5 nowych wierszy dla użytkownika w mojej niestandardowej bazie danych, więc mam łącznie 8 wierszy dla tego użytkownika. Następnie ponownie uruchamiam import. Pierwsze 3 elementy są aktualizowane, ale kiedy próbuję dodać czwarty wiersz, nadal otrzymuje identyfikator encji z 4. elementu kolekcji pola, próbuje go zaktualizować, ale kończy się niepowodzeniem i zwraca ten błąd:

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

Próbowałem usunąć elementy kolekcji pól za pomocą każdej z poniższych metod:

// 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();

To jest mój pełny kod:

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();
      }
    }
  }
}

Więc moje pytanie brzmi: jak usunąć elementy kolekcji pola, aby faktycznie zniknęły?

Smos
źródło
2
entity_delete_multiplejest w 100% zdecydowanie właściwym sposobem na zrobienie tego - spójrz na field_collection_field_deletefunkcję, której używa sama Field Collection do czyszczenia przedmiotów po usunięciu odnośnego pola
Clive
Bardzo dziękuję za odpowiedź, doceniam to. Czy zdarza ci się wiedzieć, jakie argumenty powinienem podać w polu field_collection_field_delete? Widzę, że podpis to field_collection_field_delete ($ entity_type, $ entity, $ field, $ instance, $ langcode i $ $ items), ale tak naprawdę nie wiem, jakie wartości należy wprowadzić: $ entity (to jest użytkownik lub kolekcja pól ?), $ field (zwracana wartość z field_collection_item_load?), $ instance, $ langcode (und?) i $ items.
Smos
1
Ta konkretna funkcja jest implementacją przechwytującą , w zasadzie po usunięciu dowolnego pola nazwa pola jest przekazywana do tej funkcji, a Kolekcja pól sprawdza, czy z tą instancją pola jest powiązany obiekt FC. Jeśli tak, usuwa go za pomocą entity_delete_multiple(). Może być konieczne uruchomienie crona kilka razy po usunięciu pól (dane pól są czyszczone zgodnie z harmonogramem, aby nie obciążać ładowania jednej strony całą obróbką do wykonania)
Clive
Ponownie spróbowałem użyć elementu entity_delete_multiple i zauważyłem, że elementy zostaną usunięte z tabeli element_kolekcji_pole, ale pola nadal istnieją w tabeli nazwa_kola_kolekcji. Myślę, że powoduje to błąd krytyczny Wywołanie niezdefiniowanej metody stdClass :: save (), ponieważ powinny to być pola, ale nie mają z nimi powiązanego elementu kolekcji pól. Kiedy używam $ element_kolekcji_kolekcji-> usuńRewizja usuwa dane z obu tabel, ale kiedy zapisuję użytkownika, wiersz jest dodawany do tabeli nazwa_kolekcji_kolekcji pole i jest to pusty element kolekcji pól.
Smos
@Smos: hej kolego, czy możesz mi pomóc z podobnym problemem ( drupal.stackexchange.com/questions/239784/... )? Próbowałem odpowiednich fragmentów twojego kodu, ale nie mogłem go uruchomić.
sisko

Odpowiedzi:

13

Natknąłem się na podobny przypadek użycia, w którym chciałem odwzorować niektóre dane na kolekcję pól podczas hook_feeds_presave (), ponieważ struktura źródła była zbyt złożona dla Feeds. Stwierdziłem, że podmiot_delete_multiple () usunął elementy kolekcji pól, ale kiedy edytowałem węzeł, wciąż było tam kilka pustych kolekcji pól. Rozbrojenie i usunięcie zrobiła sztuczka, którą znalazłem tutaj: https://drupal.stackexchange.com/a/31820/2762

Jeśli źródło kanałów uległo zmianie, usuwam wszystkie elementy kolekcji pól i ponownie tworzę. Mam nadzieję, że to jest pomocne.

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);
Vincent
źródło
To dwustopniowe podejście jest jedynym sposobem, który działa AFAIK. Nie zapomnij o node_save($node)swoim węźle.
Bernhard Fürst
@ BernhardFürst faktycznie nie potrzebujemy node_save($node), DrupalEntityController
wykonam
8

Najlepszym sposobem na zrobienie tego teraz jest połączenie telefoniczne $field_collection->delete(), które poradzi sobie ze wszystkim.

 <?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);
      }
    }
 ?>
benjy
źródło
0

Powyższe odpowiedzi nie są najlepszym sposobem, przy wyłączeniu wszystkie pozostałe przedmioty zniknęły z kolekcji pola, a na odwrót z ->delete()błędem w module Entity.

Właściwa droga. Cóż, zrobiłem to:

W moim przypadku chciałem usunąć ostatni element z kolekcji pól

zapoznaj się z tym kodem (dla początkujących: „pamiętaj, że musisz mieć już jednostkę $ załadowaną”)

// 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);

to wszystko! na odwrót jest

//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));

Powyższy sposób jest dobry przy użyciu entity_metadata_wrapperfunkcji, ale w ten sposób występuje złożony błąd, którego nie wiem, jak go rozwiązać, możesz go sprawdzić na https://drupal.org/node/1880312 i po zastosowaniu łatki w # 9 otrzymujesz następny problem, sprawdź go tutaj https://drupal.org/node/2186689 ten błąd występuje również, jeśli używasz ->delete()funkcji.

Mam nadzieję, że to komuś pomoże.

svelandiag
źródło
0

używając vbo, aby usunąć elementy kolekcji pól. automatycznie usunie relację pola z jednostką hosta elementu zbioru pola.

Terry Zhang
źródło
0

Miałem podobny problem, gdy importuję dane z pliku danych do elementu FC. Gdy aktualizacja źródła jest tworzona z kanału, a ja importuję te zmiany, chciałem się upewnić, że wszelkie istniejące elementy FC, które już nie istnieją ze źródła kanału, zostały usunięte.

Moje rozwiązanie:

// 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();

I to wszystko. Funkcja hook_field_update ( field_collection_field_update) w Field Collection zajmie się faktycznym usunięciem wszelkich istniejących elementów FC, które zostały usunięte z odniesienia.

Jedynym minusem jest to, że jeśli dane FC nie uległy zmianie, zostały one usunięte i utworzone ponownie. Ale to nie jest dla mnie wielka sprawa.

Brian
źródło