Migrate API のパイプラインを利用してマルチフィールドに複数画像をマイグレーションする例の続き。
サブフィールドの例
これまでの例では、画像フィールドの代替テキスト(alt 属性)を設定していなかった。こうした、フィールドに含まれるデータ項目はサブフィールドと呼ばれ、フィールド名の後にスラッシュで区切って名前を指定することができる。画像(画像ファイル エンティティの ID)と代替テキスト(alt 属性)を個別に指定する例を示す。
id: mgdemo_article
source:
plugin: embedded_data
data_rows:
-
myid: 1
mytitle: 吾輩は猫である
mytext: 吾輩は猫である。名前はまだない。
myimg: /tmp/img/cat1.jpg
myalt: 猫
-
myid: 2
mytitle: 羅生門
mytext: ある日の暮方の事である。一人の下人が、羅生門の下で雨やみを待っていた。
myimg: /tmp/img/gate.jpg
myalt: 門の外観
-
myid: 3
mytitle: 雪国
mytext: 好きよあなた。いまでも。いまでも。
myimg: /tmp/img/snow.jpg
myalt: 雪景色
ids:
myid:
type: integer
process:
nid: myid
title: mytitle
uid:
plugin: default_value
default_value: 1
body: mytext
field_image/target_id:
plugin: migration_lookup
migration: mgdemo_image
source: myimg
field_image/alt: myalt
destination:
plugin: 'entity:node'
default_bundle: article
field_image/target_id が画像ファイル エンティティの ID、field_image/alt が代替テキストをそれぞれ表すサブフィールドである。
このマイグレーションを実行すると、次のように画像と代替テキストがインポートされる。
フィールドタイプの多くはデフォルトのサブフィールドを定義している。その場合、サブフィールドを明示しなければデフォルトのサブフィールドに値がセットされる。たとえば、画像フィールドのデフォルトのサブフィールドは target_id なので、画像ファイル エンティティの ID を設定するだけならサブフィールドの指定は不要だ。しかし、代替テキストのようなデフォルト以外のサブフィールドに値をセットするには、サブフィールド名(alt)を明示的に指定する必要がある。
各種フィールドタイプにどんなサブフィールドがあるのかは、対象となるフィールドタイプの定義を調べる必要があるが、ありがたいことにフィールドのタイプ別にサブフィールドの一覧を整理した資料が公開されている。たとえば、画像フィールド(Entity reference image field)の項を見ると、次の5つのサブフィールドがあることがわかる。
- target_id(デフォルト)
- alt
- title
- width
- height
マルチのサブフィールドと subprocess プラグイン
Migrate API のパイプラインを利用してマルチフィールドに複数画像をマイグレーションする例では、参照先の画像ファイル エンティティの ID の配列を field_image にセットすることで、デフォルトのサブフィールドである target_id に各配列要素の値を反映させていた。では、マルチフィールドの各画像に、代替テキストのような非デフォルトのサブフィールドをセットするにはどうすれば良いか。
image_field に値をセットする処理の中で、ソースのある項目を target_id に、別の項目を alt に振り分ける、いわば子供の変換処理を image_field の配下に定義する必要がある。
こうした要求に応えるため、コアの Migrate API に subprocess プラグインが用意されている。これを利用すると(連想)配列の配列をソースとして、要素である配列のキーの値をサブフィールドにマッピングすることができる。
たとえば、次のような、画像のパスと代替テキストからなる配列を要素とする配列をソース imginfo として用意できれば、
source: Array
(
[imginfo] => Array
(
[0] => Array
(
[0] => '/tmp/img/cat1.jpg'
[1] => '猫'
)
[1] => Array
(
[0] => '/tmp/img/cat2.jpg'
[1] => 'ぬこ'
)
[2] => Array
(
[0] => '/tmp/img/cat3.jpg'
[1] => 'ねんネコ'
)
)
)
次のようなマイグレーションを記述することで、
field_image:
plugin: sub_process
source: imginfo
process:
target_id:
plugin: migration_lookup
migration: mgdemo_image
source: '0'
alt: '1'
各配列要素の先頭要素(インデックス = 0)を参照先の画像ファイル エンティティ ID に、2番目の要素(インデックス = 1)を代替テキストにそれぞれセットする処理を、マルチフィールドの各画像について実行させることができる。
process サブキーが subprocess プラグインによって実行される子供の変換処理の定義。field_image の各サブフィールドに、ソース imginfo の配列要素の対応する位置の値をマッピングしている。
フック関数の利用
問題はソース配列 imginfo をどのようにして作るかだ。種々のプラグインを駆使すれば実現できるのかもしれないが、このくらいになるともう直接 PHP のコードを書いた方が早い気もする。
Migrate API には、ソースデータを追加/加工する目的で利用できるフック関数が用意されている。
前者はすべてのマイグレーション共通、後者は特定のマイグレーション ID 限定で、変換処理が実行される前に呼び出される。ここでは後者を使ってみよう。
まず、マルチ画像フィールドに画像と代替テキストをインポートするためのソースデータとマイグレーションを次のように定義した。
id: mgdemo_article
source:
plugin: embedded_data
data_rows:
-
myid: 1
mytitle: 吾輩は猫である
mytext: 吾輩は猫である。名前はまだない。
myimg: /tmp/img/cat1.jpg, /tmp/img/cat2.jpg, /tmp/img/cat3.jpg
myalt: 猫, ぬこ, ねんネコ
-
myid: 2
mytitle: 羅生門
mytext: ある日の暮方の事である。一人の下人が、羅生門の下で雨やみを待っていた。
myimg: /tmp/img/gate.jpg
myalt: 門の写真
-
myid: 3
mytitle: 雪国
mytext: 好きよあなた。いまでも。いまでも。
myimg: /tmp/img/snow.jpg, /tmp/img/ski.jpg
myalt: 雪景色, スキーヤー
ids:
myid:
type: integer
process:
nid: myid
title: mytitle
uid:
plugin: default_value
default_value: 1
body: mytext
field_image:
plugin: sub_process
source: imginfo
process:
target_id:
plugin: migration_lookup
migration: mgdemo_image
source: '0'
alt: '1'
destination:
plugin: 'entity:node'
default_bundle: article
myimg 列が画像のフルパス、myalt 列が各画像に対応する代替テキストである。
次に、mgdemo というマシン名で簡単なモジュールを作り、マイグレーション mgdemo_article 用のフック関数を定義した。
mgdemo.module
<?php
use Drupal\migrate\Row;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Plugin\MigrationInterface;
/**
* Implements hook_migrate_MIGRATION_ID_prepare_row().
*/
function mgdemo_migrate_mgdemo_article_prepare_row(Row $row,
MigrateSourceInterface $source, MigrationInterface $migration) {
// myimage列の値をカンマ位置で分割して配列にする
$myimg = $row->getSourceProperty('myimg');
$images = explode(',', $myimg);
array_walk($images, '_trim_value');
// myalt列の値をカンマ位置で分割して配列にする
$myalt = $row->getSourceProperty('myalt');
$alts = explode(',', $myalt);
array_walk($alts, '_trim_value');
// 各配列の同位置の要素を束ねた配列の配列を作る
$imginfo = array_map(null, $images, $alts);
// imginfoという名前のソース列として追加
$row->setSourceProperty('imginfo', $imginfo);
}
/*
* array_walk用のコールバック
*/
function _trim_value(&$value) {
$value = trim($value);
}
myimg 列、myalt 列それぞれの値を explode 関数で分割して配列にし、各要素を trim 関数で処理して前後の空白を除去している。_trim_value() 関数は array_walk 関数 で trim 関数を反復適用するためのコールバックである。
最後に、array_map 関数の第1引数に null を指定することで、両配列の各要素を合体した配列の配列を生成し、imginfo という名前でソース列($row)に追加している。こうすることで、マイグレーション側では、imginfo というソース列の値として、変数 $imginfo の配列を利用できるようになる。
モジュールを有効化し、キャッシュをクリアしてから、mgdemo_article のマイグレーションを実行すると、画像フィールドに画像と代替テキストが反映する形で記事ノードがインポートされる。
まとめ
subprocess プラグインを使用して、マルチフィールドのサブフィールドに値を設定するサンプルを示した。Drupal 開発者にはおなじみのフック関数を利用すれば、マイグレーションの過程で必要となる込み入ったソースデータの構築を PHP コードで記述することも簡単にできる。