Como excluir corretamente uma coleção de campos?

9

Versão do Drupal: 7.21
Versão do módulo de coleta de campos: 7.x-1.0-beta5

Breve explicação : estou ocupado tentando importar coleções de campos de forma programática, mas ao excluir algumas delas sempre há alguma coleção de campos "falsas".

Explicação longa : meus usuários têm um campo de coleção de campos em seu perfil. Esta coleção de campos contém 3 campos de texto. Quero importar dados de um banco de dados sql personalizado para a coleção de campos do usuário. Essa coleção de campos pode ter vários valores. Quando importo os dados pela primeira vez, tudo funciona bem, vejo os dados nos campos da coleção de campos. Ótimo.

Mas aqui vem a parte complicada. Digamos que eu importe para um usuário específico 5 linhas do banco de dados personalizado. Eles são adicionados à coleção de campos, portanto, essa coleção de campos possui 5 itens, cada um contendo 3 campos. Em seguida, excluo algumas linhas do meu banco de dados personalizado para que eu tenha apenas 3 linhas restantes para este usuário. Eu executo a importação novamente, atualizando os 3 primeiros itens da coleção de campos, mas resta 2 itens da importação anterior. Eles devem ser excluídos porque tenho apenas 3 linhas importadas, mas ainda 5 itens de coleção de campos.

Tentei excluir esses itens da coleção de campos, mas sempre há um ou mais itens. Os campos estão vazios quando olho para o perfil do usuário, mas ainda há algo lá. Digamos que neste momento eu adiciono 5 novas linhas para o usuário no meu banco de dados personalizado, portanto, tenho 8 linhas no total para esse usuário. Então eu executo a importação novamente. Os três primeiros itens são atualizados, mas quando tento adicionar a quarta linha, ele ainda recebe um ID de entidade do 4º item da coleção de campos, tenta atualizá-lo, mas falha e retorna esse erro:

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

Tentei excluir os itens da coleção de campos com cada um destes métodos abaixo:

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

Este é o meu código completo:

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

Portanto, minha pergunta é: como faço para excluir itens da coleção de campos para que eles não sejam mais usados?

Smos
fonte
2
entity_delete_multipleé 100% definitivamente o caminho certo para fazê-lo - ter um olhar para a field_collection_field_deletefunção, que é o que coleção de campo em si usa para limpar itens quando o campo referenciado é removido
Clive
Muito obrigado pela sua resposta, agradeço. Você sabe quais argumentos devo fornecer com field_collection_field_delete? Vejo que a assinatura é field_collection_field_delete ($ entity_type, $ entity, $ field, $ instance, $ langcode e $ items), mas não sei realmente quais valores colocar: $ entity (esse é o usuário ou a coleção de campos ?), $ field (valor de retorno de field_collection_item_load?), $ instance, $ langcode (und?) e $ items.
Smos 04/04
11
Essa função específica é uma implementação de gancho, basicamente, quando qualquer campo é excluído, o nome do campo é passado para essa função, e a Coleção de Campos verifica se há uma entidade FC associada a essa instância de campo. Se houver, ele será excluído usando entity_delete_multiple(). Talvez seja necessário executar cron um par de vezes depois que você campos de exclusão (dados de campo são purgados em uma programação de modo a não sobrecarregar um único carregamento da página com tudo o que o processamento de fazer)
Clive
Tentei usar entity_delete_multiple novamente e notei que os itens são excluídos na tabela field_collection_item, mas os campos ainda existem na tabela field_data_field_collection_name. Eu acho que isso causa o erro fatal Call ao método indefinido stdClass :: save () porque eles deveriam ser campos, mas eles não têm um item de coleção de campos vinculado a ele. Quando eu uso $ field_collection_item-> deleteRevision, ele exclui os dados nas duas tabelas, mas quando eu salvo o usuário, uma linha é adicionada à tabela field_data_field_collection_name e esse é um item de coleção de campos em branco.
Smos 04/04
@Mos: ei amigo, você pode me ajudar com um problema semelhante ( drupal.stackexchange.com/questions/239784/… )? Tentei bits relevantes do seu código, mas não consegui fazê-lo funcionar.
Ssko

Respostas:

13

Encontrei um caso de uso semelhante em que queria mapear alguns dados em uma coleção de campos durante hook_feeds_presave (), pois a estrutura de origem era muito complexa para Feeds. Descobri que entity_delete_multiple () removeu os itens da coleção de campos, mas quando editei o nó, ainda havia um monte de coleção de campos vazios lá. Desativar e excluir fez o truque, que encontrei aqui: https://drupal.stackexchange.com/a/31820/2762

Se a fonte dos feeds mudou, excluo todos os itens da coleção de campos e recrio. Espero que isso seja útil.

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
fonte
Essa abordagem em duas etapas é a única maneira que funciona no AFAIK. Não se esqueça do node_save($node)seu nó.
Bernhard Fürst
@ BernhardFürst realmente não precisamos node_save($node), DrupalEntityControllervai fazer este trabalho
coffeduong
8

A melhor maneira de fazer isso agora é ligar $field_collection->delete()e isso vai lidar com tudo.

 <?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
fonte
0

As respostas acima não são a melhor maneira, com todos os outros itens desaparecidos da coleção de campos, e a outra maneira ->delete()gera um erro no módulo Entidade.

Maneira correta. Bem, o que eu fiz foi isso:

No meu caso, eu queria excluir o último item da coleção de campos

veja este código (para iniciantes: "lembre-se de que você deve ter a entidade $ já carregada")

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

Isso é tudo! o outro lado é

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

A maneira acima é boa usando a entity_metadata_wrapperfunção, mas dessa forma existe um bug complexo que eu não sei como resolvê-lo, você pode verificá-lo em https://drupal.org/node/1880312 e depois aplicar o patch no # 9 Para obter a próxima edição, verifique aqui https://drupal.org/node/2186689 esse bug também ocorre se você usar a ->delete()função.

Espero que ajude alguém.

svelandiag
fonte
0

usando o vbo para excluir os itens da coleção de campos. ele removerá automaticamente a relação de campo com a entidade host do item de coleção de campos.

terry zhang
fonte
0

Eu tive um problema semelhante, onde estou importando dados de um feed para um item do FC. Quando uma atualização para uma entidade host é feita a partir do feed, e eu estou importando essas alterações, eu queria garantir que todos os itens FC existentes que não existem mais da fonte do feed fossem removidos.

Minha solução:

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

E é isso. Hook_field_update ( field_collection_field_update) da Field Collection se encarregará de realmente excluir quaisquer itens de FC existentes que tenham sido referenciados.

A única desvantagem disso é que, se não houver alterações nos dados do FC, eles serão excluídos e recriados de qualquer maneira. Mas isso não é grande coisa para mim.

Brian
fonte