web-dev-qa-db-ja.com

コンテンツエンティティに添付されたフィールドのタイプを変更するにはどうすればよいですか?

だから私はこれに数時間頭をぶつけてきました、そしておそらく私はあきらめてモジュールをアンインストール/再インストールします(そして古い_entity_reference_フィールドに依存するビューを更新し、こうして失敗します)。

私のユースケースについて少し:ファイルを指すいくつかの_entity_reference_フィールドを最初に作成しました。その後、Fileモジュールはfileフィールドのファイル使用状況のみを自動的に追跡することを学びました。フィールドをfileフィールドに変換し、データを保持したい。しかし、これは不可能のようです。

次の更新コードを検討してください。

_/**
 * Use file fields instead of entity_reference fields for referring to files.
 */
function fillpdf_update_8103() {
  $edum = \Drupal::entityDefinitionUpdateManager();
  $em = \Drupal::entityManager();

  $form_file_def = BaseFieldDefinition::create('file')
    ->setLabel(t('The associated managed file.'))
    ->setDescription(t('The associated managed file.'))
    ->setName('file')
    ->setProvider('fillpdf_form')
    ->setTargetBundle(NULL)
    ->setTargetEntityTypeId('fillpdf_form');

  $fc_file_def = BaseFieldDefinition::create('file')
    ->setLabel(t('The associated managed file.'))
    ->setDescription(t('The associated managed file.'))
    ->setName('file')
    ->setProvider('fillpdf_file_context')
    ->setTargetBundle(NULL)
    ->setTargetEntityTypeId('fillpdf_file_context');

  // Save existing fillpdf_form data.
  $form_files = [];
  $forms = $em->getStorage('fillpdf_form')->loadMultiple();
  foreach ($forms as $form) {
    $form_files[$form->id()] = $form->file->target_id;
    $form->file = NULL;
    $form->save();
  }

  // Save existing fillpdf_file_context data.
  $fc_files = [];
  $fcs = $em->getStorage('fillpdf_file_context')->loadMultiple();
  foreach ($fcs as $fc) {
    $fc_files[$fc->id()] = $fc->file->target_id;
    $fc->file = NULL;
    $fc->save();
  }

  // Now install the new field definitions.
  $edum->updateFieldStorageDefinition($form_file_def);
  $edum->updateFieldStorageDefinition($fc_file_def);

  foreach ($form_files as $entity_id => $fillpdf_form_file) {
    /** @var ContentEntityInterface $entity */
    $entity = $em->getStorage('fillpdf_form')->load($entity_id);
    $entity->file->target_id = $fillpdf_form_file;
    $entity->save();

    // We want postSave() hooks to run so that file usage gets recorded.
    // @see ContentEntityStorageBase::invokeFieldMethod().
    $result = [];
    // So $update = FALSE gets passed to the handler.
    $args = [FALSE];
    $method = 'postSave';
    foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
      $translation = $entity->getTranslation($langcode);
      // For non translatable fields, there is only one field object instance
      // across all translations and it has as parent entity the entity in the
      // default entity translation. Therefore field methods on non translatable
      // fields should be invoked only on the default entity translation.
      $fields = $translation->isDefaultTranslation() ? $translation->getFields() : $translation->getTranslatableFields();
      foreach ($fields as $name => $items) {
        if ($name === 'file') {
          // call_user_func_array() is way slower than a direct call so we avoid
          // using it if have no parameters.
          $result[$langcode][$name] = $args ? call_user_func_array([
            $items,
            $method,
          ], $args) : $items->{$method}();
        }
      }
    }
  }

  foreach ($fc_files as $entity_id => $ffcf) {
    /** @var ContentEntityInterface $entity */
    $entity = $em->getStorage('fillpdf_file_context')->load($entity_id);
    $entity->file->target_id = $ffcf;
    $entity->save();

    // We want postSave() hooks to run so that file usage gets recorded.
    // @see ContentEntityStorageBase::invokeFieldMethod().
    $result = [];
    // So $update = FALSE gets passed to the handler.
    $args = [FALSE];
    $method = 'postSave';
    foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
      $translation = $entity->getTranslation($langcode);
      // For non translatable fields, there is only one field object instance
      // across all translations and it has as parent entity the entity in the
      // default entity translation. Therefore field methods on non translatable
      // fields should be invoked only on the default entity translation.
      $fields = $translation->isDefaultTranslation() ? $translation->getFields() : $translation->getTranslatableFields();
      foreach ($fields as $name => $items) {
        if ($name === 'file') {
          // call_user_func_array() is way slower than a direct call so we avoid
          // using it if have no parameters.
          $result[$langcode][$name] = $args ? call_user_func_array([
            $items,
            $method,
          ], $args) : $items->{$method}();
        }
      }
    }
  }
}
_

$field->postSave()を実行するためのハッキングについてあまり心配しないでください。それは実際にはうまくいきます。

主な問題は、更新を実行する前にFieldStorageDefinitionfileを更新すると、loadMultiple()の呼び出しが新しいスキーマを使用しようとするために失敗することです。

コードをそのままにしておくと(スキーマを変更しない)、問題なく実行されますが、古いフィールドタイプを使用してデータが再保存されるため、postSaveフックは実際には実行されません。 。少なくとも私が欲しいものではない、FileFieldItemList::postSave()

変更レコード がデータベースを使用してデータを保存し、再挿入するのは驚くべきことではありません。そうでなければ、前述のスキーマエラーが発生するため、これはおそらく意図的なものです。しかし、それを行うことは決してポータブルではありません。つまり、SQLストレージを使用していない場合はどうなりますか?

私の頭の中で理想的なソリューションは、スキーマを更新する前に古い定義に基づいてストレージマネージャーにエンティティを取得させ、更新後に新しい定義を取得できるようにすることです。ただし、古いスキーマを取得するロジックは、保護された機能(例: SqlContentEntityStorage::doLoadMultiple()

サポート情報:これは、モジュール内のスキーマを更新した後に更新を実行しようとすると発生するエラーです。

_fillpdf module

Update #8103

- Failed: Drupal\Core\Database\DatabaseExceptionWrapper: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'base.file__target_id' in 'field list': SELECT base.fid AS fid, base.uuid AS uuid, base.file__target_id AS file__target_id, base.file__display AS file__display, base.file__description AS file__description, base.admin_title AS admin_title, base.title AS title, base.default_entity_type AS default_entity_type, base.default_entity_id AS default_entity_id, base.destination_path AS destination_path, base.scheme AS scheme, base.destination_redirect AS destination_redirect FROM {fillpdf_forms} base; Array ( ) in Drupal\Core\Entity\Sql\SqlContentEntityStorage->getFromStorage() (line 429 of /var/www/site/docroot/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
_

新しいフィールドを作成し、データを移動し、古いフィールドを廃止する唯一のオプションはありますか?スキーマストレージがこのように機能している間は、everで削除できるとは思わないので、これを行いたくありません。エンティティをロードしようとすると、将来の更新が失敗します。

4
wizonesolutions

IRCでchxとcatchを使用して、hook_update_N()がSQLのみの世界であることを知りました。私はこれを述べて問題を見つけられませんでした明らかに、しかしhook_update_N()自体はこれを言っています:

API関数、特に更新関数で使用するCRUD操作に注意してください。フックを呼び出したりサービスを使用したりすると、期待どおりに動作しない可能性があり、実際には、すべてのフックを呼び出す通常のAPI関数を使用したり、データベーススキーマを使用したり、更新関数でサービスを使用したりすることは適切ではありません-より直接的な方法(データベースクエリなど)に切り替える必要がある場合があります。
特に、エンティティに対して他のCRUD操作をロード、保存、または実行することは安全ではありません(常にフックとサービスが関係します)。

それで、それを念頭に置いて、私の完成した更新フックは次のようになりました。

<?php
/**
 * Use file fields instead of entity_reference fields for referring to files.
 */
function fillpdf_update_8103() {
  $edum = \Drupal::entityDefinitionUpdateManager();
  $em = \Drupal::entityManager();
  $db = \Drupal::database();

  $form_file_def = BaseFieldDefinition::create('file')
    ->setLabel(t('The associated managed file.'))
    ->setDescription(t('The associated managed file.'))
    ->setName('file')
    ->setProvider('fillpdf_form')
    ->setTargetBundle(NULL)
    ->setTargetEntityTypeId('fillpdf_form');

  $fc_file_def = BaseFieldDefinition::create('file')
    ->setLabel(t('The associated managed file.'))
    ->setDescription(t('The associated managed file.'))
    ->setName('file')
    ->setProvider('fillpdf_file_context')
    ->setTargetBundle(NULL)
    ->setTargetEntityTypeId('fillpdf_file_context');

  // Save existing data.
  $form_files = $db->select('fillpdf_forms', 'ff')
    ->fields('ff', ['fid', 'file'])
    ->execute()
    ->fetchAllKeyed();

  $fc_files = $db->select('fillpdf_file_context', 'fc')
    ->fields('fc', ['id', 'file'])
    ->execute()
    ->fetchAllKeyed();

  // Remove data from the storage.
  $db->update('fillpdf_forms')
    ->fields(['file' => NULL])
    ->execute();

  $db->update('fillpdf_file_context')
    ->fields(['file' => NULL])
    ->execute();

  // Now install the new field definitions.
  $edum->updateFieldStorageDefinition($form_file_def);
  $edum->updateFieldStorageDefinition($fc_file_def);

  foreach ($form_files as $entity_id => $fillpdf_form_file) {
    /** @var ContentEntityInterface $entity */
    $entity = $em->getStorage('fillpdf_form')->load($entity_id);
    $entity->file->target_id = $fillpdf_form_file;
    $entity->save();
  }

  foreach ($fc_files as $entity_id => $ffcf) {
    /** @var ContentEntityInterface $entity */
    $entity = $em->getStorage('fillpdf_file_context')->load($entity_id);
    $entity->file->target_id = $ffcf;
    $entity->save();
  }
}
?>

CRUDを使用しないことについては無視していることに注意してください。スキーマが更新された後に使用します。私が目にした種類のINSERTクエリ https://www.drupal.org/node/2554097#change-field-schema を処理したくありませんでした。うまくいきました。より複雑なエンティティを扱う場合、マイレージはそこで異なる場合がありますが、それは私にとってはうまくいきました。

1
wizonesolutions