想定するサイト構成
- ローカル開発環境、社内開発環境、ステージングサイト、本番サイトなど複数の環境を運用していることを前提とする
- Drupal 8の Configration Management(以下、CMI)とGitを使って、サイト構成をデプロイすることを前提とする
- 単一の環境を使用している場合は、ブロックコンテンツをデプロイする必要はなく、ここで取り上げた課題について心配無用
検証環境
- Drupal8.4.4
これより古いバージョンの場合は、CMIのエラーメッセージ表示のタイミングが異なる。
ブロックコンテンツをエクスポート/インポートすることはできない
- ブロック設定は、ブロックレイアウトとブロックコンテンツの2つのエンティティで構成されてる。その内、ブロックレイアウトだけがCMI管理されている。
- CMIは、コンテンツデータのエクスポート/インポートは対応していない。
ブロックコンテンツを設定してはいけない
つまり、CMIを利用する場合は、カスタムレイアウト画面上でブロックコンテンツを追加してはいけません。
もし、追加してしまった場合は、CMIインポート後にブロックの設定画面を開くと「 Block description Broken/Missing 」というエラーメッセージが表示されてしまう。
CMIエクスポート時に警告してくれるわけではないので気づきにくく、また、CMIを通じてブロックコンテンツのエクスポートを除外するようなオプションもない。
この問題に関してdrupal.orgでは触れておらず、対処に関しても明確なドキュメントはありません。
Broken/Missingエラーの再現手順
開発環境:
- カスタムブロックの作成
- このカスタムブロックを任意のリージョンに割り当てる
- サイトの設定をCMIエクスポートする
ステージング環境/本番環境:
- 開発環境から設定をCMIインポートする
- ブロックレイアウト画面に遷移すると、一応、リージョンにはカスタムブロックが配置されている
- カスタムブロックの編集ボタンをクリックすると、エラーメッセージが表示される
- サイト表示させてみても意図したコンテンツは表示されていない状態
このようにブロックレイアウトだけがエクスポート/インポートされ、ブロックコンテンツとの依存関係が壊れてしまう。
解決策(様々なコンテンツデプロイ手段)
推奨案
CMIを通してブロックコンテンツをエクスポートしない
その他、コンテンツをデプロイする手段
- updateフックを使って、コード上でブロックコンテンツ作成
参考)https://www.drupal.org/project/drupal/issues/2756331#comment-11994279 - 本文を含むカスタムブロックプラグインを実装する
https://www.drupal.org/docs/8/creating-custom-modules/create-a-custom-block - デプロイ開発環境のDBからブロック関連のテーブルだけをダンプ&インポート
- config_ignoreモジュール
https://www.drupal.org/project/config_ignore - simple_blockモジュール
https://www.drupal.org/project/simple_block - Deployモジュール
https://www.drupal.org/project/deploy - Default Content for D8 モジュール
https://www.drupal.org/project/default_content
このように多くの手段があるが、それでもブロックコンテンツを本番サイト上で個別に編集したいという要望に応えられない。
さらにこれらを実施したときは、本番サイト上で編集していた内容が上書きされてしまう危険もある。
最適解(環境ごとにコンテンツ編集を可能にする)
いま社内で運用している方法として、ブロックレイアウトとブロックコンテンツを一意のコードで紐付ける方法を採っている。
このカスタムブロックコンテンツタイプは、CIM管理される。
カスタムブロックコンテンツは、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キャッシュクリア後、ブロックレイアウト画面からプラグインを呼び出し可能になる。
例)ブロックレイアウト - カスタムブロックプラグインを配置する
例)ブロックレイアウト - カスタムブロックプラグイン配置完了
以上、ブロックレイアウト画面上での設定内容はCIM管理される。
実装したカスタムブロックプラグインは、指定のマシンネームを持つブロックコンテンツが見つかったときだけロードするので、環境ごとにブロックコンテンツを作成して本文(HTMLコード)の編集が可能になる。