Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Article information
RevisionsShow article in Markdown
Report article
Help us understand the problem. What is going on with this article?

More than 3 years have passed since last update.

This post is Private. Only a writer or those who know its URL can access this post.

@J_Sugar__

ブロックコンテンツがデプロイできない問題と最適解

ブロックコンテンツがデプロイできない問題と最適解

by J_Sugar__
1 / 21

想定するサイト構成

  • ローカル開発環境、社内開発環境、ステージングサイト、本番サイトなど複数の環境を運用していることを前提とする
  • Drupal 8の Configration Management(以下、CMI)とGitを使って、サイト構成をデプロイすることを前提とする
  • 単一の環境を使用している場合は、ブロックコンテンツをデプロイする必要はなく、ここで取り上げた課題について心配無用

検証環境

  • Drupal8.4.4
    これより古いバージョンの場合は、CMIのエラーメッセージ表示のタイミングが異なる。

ブロックコンテンツをエクスポート/インポートすることはできない

  • ブロック設定は、ブロックレイアウトとブロックコンテンツの2つのエンティティで構成されてる。その内、ブロックレイアウトだけがCMI管理されている。
  • CMIは、コンテンツデータのエクスポート/インポートは対応していない。

ブロックコンテンツを設定してはいけない

つまり、CMIを利用する場合は、カスタムレイアウト画面上でブロックコンテンツを追加してはいけません。

もし、追加してしまった場合は、CMIインポート後にブロックの設定画面を開くと「 Block description Broken/Missing 」というエラーメッセージが表示されてしまう。

Screen Shot 2018-05-22 at 11.53.53.png


CMIエクスポート時に警告してくれるわけではないので気づきにくく、また、CMIを通じてブロックコンテンツのエクスポートを除外するようなオプションもない。

この問題に関してdrupal.orgでは触れておらず、対処に関しても明確なドキュメントはありません。


Broken/Missingエラーの再現手順

開発環境:

  1. カスタムブロックの作成
  2. このカスタムブロックを任意のリージョンに割り当てる
  3. サイトの設定をCMIエクスポートする

Screen Shot 2018-05-21 at 22.17.12.png


Screen Shot 2018-05-21 at 22.20.24.png


ステージング環境/本番環境:

  1. 開発環境から設定をCMIインポートする
  2. ブロックレイアウト画面に遷移すると、一応、リージョンにはカスタムブロックが配置されている
  3. カスタムブロックの編集ボタンをクリックすると、エラーメッセージが表示される
  4. サイト表示させてみても意図したコンテンツは表示されていない状態

このようにブロックレイアウトだけがエクスポート/インポートされ、ブロックコンテンツとの依存関係が壊れてしまう。


解決策(様々なコンテンツデプロイ手段)

推奨案

CMIを通してブロックコンテンツをエクスポートしない


その他、コンテンツをデプロイする手段

  1. updateフックを使って、コード上でブロックコンテンツ作成
    参考)https://www.drupal.org/project/drupal/issues/2756331#comment-11994279
  2. 本文を含むカスタムブロックプラグインを実装する
    https://www.drupal.org/docs/8/creating-custom-modules/create-a-custom-block
  3. デプロイ開発環境のDBからブロック関連のテーブルだけをダンプ&インポート
  4. config_ignoreモジュール
    https://www.drupal.org/project/config_ignore
  5. simple_blockモジュール
    https://www.drupal.org/project/simple_block
  6. Deployモジュール
    https://www.drupal.org/project/deploy
  7. Default Content for D8 モジュール
    https://www.drupal.org/project/default_content

このように多くの手段があるが、それでもブロックコンテンツを本番サイト上で個別に編集したいという要望に応えられない。
さらにこれらを実施したときは、本番サイト上で編集していた内容が上書きされてしまう危険もある。


最適解(環境ごとにコンテンツ編集を可能にする)

いま社内で運用している方法として、ブロックレイアウトとブロックコンテンツを一意のコードで紐付ける方法を採っている。


例)ブロックコンテンツタイプ - フィールドの管理
Screen Shot 2018-05-22 at 17.38.18.png

このカスタムブロックコンテンツタイプは、CIM管理される。


例)ブロックコンテンツ編集画面
Screen Shot 2018-05-22 at 17.29.18.png

カスタムブロックコンテンツは、CIM管理されない。そのため、サイトごとに任意に設定可能な内容になる。

以上、ブロックコンテンツが用意できたら、次はブロックレイアウトと紐付けるためのブロックプラグインを実装していく。


例)カスタムブロックプラグイン「埋込ブロックコンテンツ」

namespace Drupal\my_module\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\block_content\Entity\BlockContent;
use Drupal\Core\Language\LanguageManagerInterface;

/**
* Provides embed a custom block to an any region.
*
* @Block(
*   id = "my_module_block_custom_content_block",
*   admin_label = @Translation("Embed custom block content"), //ラベル名:埋込ブロックコンテンツ
*   category = @Translation("My Module")
* )
*/
class EmbedCustomBlockContentBlock extends BlockBase implements ContainerFactoryPluginInterface {

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Language manager.
   *
   * @var \Drupal\Core\Language\LanguageManagerInterface
   */
  protected $languageManager;

  /**
   * Constructs a new EntityView.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin ID for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, LanguageManagerInterface $language_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->entityTypeManager = $entity_type_manager;
    $this->languageManager = $language_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->get('entity_type.manager'),
      $container->get('language_manager')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'custom_block_machine_name' => '',
      'custom_block_view_mode' => 'default',
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function blockForm($form, FormStateInterface $form_state) {
    $form['custom_block_machine_name'] = [
      '#title' => $this->t('Machine name for a custom block content'),
      '#type' => 'textfield',
      '#description' => $this->t("Please set a machine name of a custom block."),
      '#default_value' => $this->configuration['custom_block_machine_name'],
      '#required' => TRUE,
      '#placeholder' => t('ex) unique_machine_name'),
    ];

    $node_view_modes = \Drupal::service('entity_display.repository')->getViewModes('block_content');
    $node_view_modes_options = ['' => $this->t('-Select-')];
    foreach ($node_view_modes as $key => $view_mode) {
      $node_view_modes_options[$key] = $view_mode['label'];
    }
    $form['custom_block_view_mode'] = [
      '#title' => $this->t('View mode'),
      '#type' => 'select',
      '#options' => $node_view_modes_options,
      '#description' => $this->t("Please select a view mode of the Embed Content."),
      '#default_value' => $this->configuration['custom_block_view_mode'],
      '#required' => FALSE,
      '#placeholder' => t('ex) default'),
    ];
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function blockSubmit($form, FormStateInterface $form_state) {
    $this->configuration['custom_block_machine_name'] = $form_state->getValue('custom_block_machine_name');
    $this->configuration['custom_block_view_mode'] = $form_state->getValue('custom_block_view_mode');
  }

  /**
   * Builds the renderable array for this Entity Embed display plugin.
   *
   * @return array
   *   A renderable array representing the content of the embedded entity.
   */
  public function build() {
    $build = [];
    $machine_name = $this->configuration['custom_block_machine_name'];
    if ($machine_name) {
      $query = $this->entityTypeManager->getStorage('block_content')->getQuery();
      $query->condition('type', 'basic');
      $query->condition('field_machine_name', $machine_name);
      $bids = $query->execute();
    }
    else {
      return $build;
    }

    if ($block_contents = BlockContent::loadMultiple($bids)) {
      $view_mode = $this->configuration['custom_block_view_mode'] ?? 'default';
      $langcode = $this->languageManager->getCurrentLanguage()->getId();
      $render_controller = $this->entityTypeManager->getViewBuilder('block_content');
      $build['content'] = $render_controller->viewMultiple($block_contents, $view_mode, $langcode);
    }

    return $build;
  }

}

実装が完了したら、Drupalキャッシュクリア後、ブロックレイアウト画面からプラグインを呼び出し可能になる。


例)ブロックレイアウト - カスタムブロックプラグインを配置する
Screen Shot 2018-05-22 at 17.56.51.png


例)ブロックレイアウト - カスタムブロックプラグイン設定
Screen Shot 2018-05-22 at 18.04.50.png


例)ブロックレイアウト - カスタムブロックプラグイン配置完了
Screen Shot 2018-05-22 at 18.06.41.png

以上、ブロックレイアウト画面上での設定内容はCIM管理される。


実装したカスタムブロックプラグインは、指定のマシンネームを持つブロックコンテンツが見つかったときだけロードするので、環境ごとにブロックコンテンツを作成して本文(HTMLコード)の編集が可能になる。

Article information
RevisionsShow article in Markdown
Report article
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Article information
RevisionsShow article in Markdown
Report article
Help us understand the problem. What is going on with this article?