この記事は、Drupal アドベントカレンダー 2020 の 10 日目です。
(延長戦での参加なので記事の日付が大幅に遅れてます…すみません)
Migrate API で画像ファイルを取り込むサンプルの続き。
前の例では、画像フィールド(field_image)の多重度「許容する値の数」は1だった(standard インストール プロファイルの初期設定)。ここでは、複数画像を設定できるマルチフィールドのサンプルを紹介する。
まず、インポート済みのエンティティ(記事ノード、画像ファイル)をすべてロールバックしてから、「記事」コンテンツタイプの画像フィールドの「フィールドの設定」で、「許容する値の数」を「無制限」に変更しておく。
(パス:/admin/structure/types/manage/article/fields/node.article.field_image/storage)
これでマルチの画像フィールドになった。このフィールドに複数の画像をインポートするには、対応する画像エンティティの ID の配列をセットすればよい。
例として、タイトル別に異なる複数画像をインポートするマイグレーションを考える:
- 吾輩は猫である
- 羅生門
- /tmp/img/gate.jpg
- 雪国
まず、画像ファイルを画像エンティティとしてインポートするマイグレーションは次のとおり:
id: mgdemo_image
source:
constants:
FILE_DIRECTORY: 'public://'
plugin: embedded_data
data_rows:
-
myimg: /tmp/img/cat1.jpg
-
myimg: /tmp/img/cat2.jpg
-
myimg: /tmp/img/cat3.jpg
-
myimg: /tmp/img/gate.jpg
-
myimg: /tmp/img/snow.jpg
-
myimg: /tmp/img/ski.jpg
ids:
myimg:
type: string
process:
tmp_srcpath: myimg
tmp_filename:
plugin: callback
callable: basename
source: myimg
tmp_destpath:
plugin: concat
source:
- constants/FILE_DIRECTORY
- '@tmp_filename'
uri:
plugin: file_copy
source:
- '@tmp_srcpath'
- '@tmp_destpath'
file_exists: replace
move: false
destination:
plugin: 'entity:file'
source: 配下の埋め込みレコードとして、ノードから参照させる画像ファイルを列挙している。ファイルのフルパスをレコードの一意識別子として使用している。
次に、上でインポートした画像エンティティを参照する記事ノードのマイグレーション例を示す:
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'
-
myid: 2
mytitle: 羅生門
mytext: ある日の暮方の事である。一人の下人が、羅生門の下で雨やみを待っていた。
myimg: '/tmp/img/gate.jpg'
-
myid: 3
mytitle: 雪国
mytext: 好きよあなた。いまでも。いまでも。
myimg: '/tmp/img/snow.jpg, /tmp/img/ski.jpg'
ids:
myid:
type: integer
process:
nid: myid
title: mytitle
uid:
plugin: default_value
default_value: 1
body: mytext
field_image:
-
plugin: explode
delimiter: ','
source: myimg
-
plugin: callback
callable: trim
-
plugin: migration_lookup
migration: mgdemo_image
destination:
plugin: 'entity:node'
default_bundle: article
ソースデータでは、myimg 列に1つ以上の画像パス(画像の一意識別子)をカンマ区切りで指定している。
これを元に目的を果たすには、次のような複数ステップの処理が必要になる:
- myimg 列のカンマ区切り部分文字列を切り出して配列にする。
- カンマ前後の空白を除去する。
- 画像パス(画像の一意識別子)を基に対応するインポート済みエンティティを照会する。
Migrate API では、連続する複数の変換処理を実行する仕組みとしてパイプラインが利用できる。具体的には、各処理(プラグインとパラメータの指定)を配列要素(-)として列挙すれば、それらを連結して実行してくれる。この例では、次の3つのプラグインによる処理を列挙している。
- explode プラグイン
PHP の explode 関数と同様の働きをする。入力(source キーの値)に指定した文字列を区切り文字(delimiter キーの値)で分割した結果を配列として出力する。この例では、myimg 列の値をカンマ位置で分割した部分文字列の配列が出力される。 - callback プラグイン
callable に指定した名前の PHP 関数を呼び出し、入力値を引数として渡して、その戻り値を出力する。この例では、trim 関数を呼び出して、前処理から渡ってきた文字列の前後にある空白文字列を除去している。 - migration_lookup プラグイン
入力値をキーとして、migration キーの値として指定した名前の既存マイグレーションからインポート済みエンティティの ID を照会して返却する。この例では、前処理から渡ってきた画像ファイルのフルパスをキーに、対応するインポート済み画像エンティティの ID を返却する。
パイプラインにおいては、前の処理の出力(返却値)が次の処理の入力(source)として認識される。したがって、明示的に source キーの値を指定するのは先頭の要素(上の例では explode)のみで、それ以外は前段の出力が source キーに指定されたものとして処理を実行する。
また、Migrate API の process プラグインには、出力値が単独の値なのか複数の値のリスト(配列)なのかを識別する仕組みがあり、配列を渡されたプラグインはその各要素に処理を適用することができる。上の例では、explode プラグインの multiple() は TRUE を返却するので、次ステップの callback プラグインの処理は、explode プラグインの出力値そのものではなく、その各配列要素に適用される。
他方、callback プラグインの multiple() は基底クラスをオーバーライドしておらず、既定の FALSE を返却する。しかし、migration_lookup プラグインは単独のソースキー値でも、その配列でも受け取れるようになっており、上の例では配列を引き渡している。migration_lookup の出力は、入力が単独であれ複数であれ配列となる。これは、実は Drupal 側のフィールドの内部構造も同じである。
以上の処理を実行すると、複数の画像を設定した記事をインポートすることができる。
このように、複数ステップの処理が必要な変換処理では Migrate API のパイプラインが利用できる。パイプラインの過程で配列を受け渡す場合は、その各要素に処理を適用する仕組みがある点にも注目しておきたい。