Advanced Custom Fieldsの便利さを享受しながら、クローズド化の罠を回避する
Advanced Custom Fields(以下ACF)が世に出てから4年は経つでしょうか。いくつかスマートげなプラグインも出たものの、いまだにシェアは圧倒的。今ひとつよくわからん機能があっても、管理・入力補助にすぐれ、結局クラインアントワークでは欠かせないんですよね。
しかし出力に独特の癖があるACF。この先、プラグインの乗り換えを検討する時が来るかもしれません。カスタムフィールドはWPで今後も利用されていくでしょうが、ACFに依存するテーブルデータや出力関数があるせいでプラグインやテーマを互換性がないなんて、オープンプラットフォームを使っている意味がないと言っても過言じゃない。そこで独自関数やテーブルを利用しないで済ませる方策を考えていきます。
チェックボックスなど複数の値を持つvalueを出力する
WPでは同じキーを持つカスタムフィールドを複数登録できますが、ACFはチェックボックスやセレクトボックス、関連記事などのフィールドは複数の値を配列のままストアされます。プラグインが配布された当初から指摘されていたように、ACFのシリアライズされた値の保持方法はmeta queryや検索との相性がよくありません。しかしqueryに載せるならwp3.0以降はカスタムタクソノミーが設定できますし、その方がパフォーマンスもいいですので、シリアライズされることの一番の問題は抽出方法ということになるかと思います。(余談ですが、WP自体はカスタムフィールドに文字列以外が入ることも想定した設計になっていますし、配列を持たせることで判定と出力がシンプルにできるので、一概にダメな設計ではないように思います。)
さてさて、この複数値を出力する方法としては、ほとんどのサイトが独自関数get_field(またはthe_field)を使うように案内しています。
// 従来のACFの配列を出力するコード
$fields = get_field('field');
if($fields): foreach($fields as $val):
echo $val;
endforeach; endif
…でもなんのことはない、値が配列になっているだけなので、以下のようにすれば出力できます。これによってテンプレートはユニバーサルな状態に保っておくことができます。
get_post_metaを使って配列を出力する
$fields = get_post_meta( $post->ID, 'field', true );
if ( $fields ): foreach( $fields as $key => $val ):
echo $val;
endforeach; endif;
Repeater fieldを独自関数を使わずにアウトプットする
初めて有料で購入したWPのプラグイン拡張だった様な気がします。25$。これは買ってください。
相当数の人が使用していると思われるリピーターフィールドですが、これも出力方法に癖があります。公式に案内されているのは、次のような感じです。ゴリゴリのプラグイン依存ですね。
// 従来のrepeater fieldを出力するループ関数
if( have_rows('field') ): while( have_rows('field') ): the_row();
$subfield = get_sub_field('subfield');
// do something with subfield
endwhile; endif;
これは以下のようにPHPとget_post_metaで表現することが出来ます。こうすればデータベースに値があれば、 ACFなしで動作します。何を求めているコードなのかPHP的に一目瞭然なのが精神衛生上いいですね。
get_post_metaとforループで配列を出力する
$n = get_post_meta( $post->ID, 'field', true);
if ($n): for ($i = 0; $i < $n; $i++ ):
$subfield = get_post_meta( $post->ID, 'field_'.$i. '_subfield', true );
// do something with subfield
endfor; endif;
実地に合わせるとこんな感じに落ち着きます。
$n = get_post_meta( $post->ID, 'item', true);
if ($n): for ($i = 0; $i < $n; $i++ ):
$image = get_post_meta( $post->ID, 'item_'.$i. '_image', true );
if ( $image ) {
echo '<div class="image">'.wp_get_attachment_image ( $image, 'medium' ).'</div>'."n";
}
$text = get_post_meta( $post->ID, 'item_'.$i. '_text', true );
if ( $text ) {
echo '<div class="text">'.wpautop( $text ).'</div>'."n";
}
endfor; endif;
カテゴリのカスタムフィールドをwp term meta対応にする
※ACF5(2019〜)の途中あたりからから正式にterm_metaに対応したようなので以下は不要です。
カテゴリに画像や注釈を付与するという要件は、CMSではよくある話です。カテゴリのカスタムフィールド設定は、なかなか満足できるプラグインがなくACFを使わざるをえず、ACFの独自データテーブルに依存するのを避けられませんでした。しかしwp4.4から公式にカテゴリやタグなどのタームはデフォルトでメタフィールド(wp term meta)が設置可能になりました。当初すぐにACFもサポートするものだと思っていましたが、半年待っても音沙汰なく、今後対応するかどうかも一切不明。wp term metaに対応すると、ユーザー囲い込みの一端が解けることを嫌ってるんじゃないかとか邪推したくなります。公式の機能からかけ離れていくのもどうかと思うんですけどね。
で、ACFのフォーラムで素晴らしい発案(How to use WP Term Meta, the easy way!)があったので私もそれを採用することにしました。ACF側でタームメタが更新された時に、自動的にwp term metaを上書きするだけです。逆方向はどうするんだとか、疑問はあるかもしれませんが、基本的には、wp term metaにストアさえされていれば、get_fieldを使わずに公式のget_term_metaで出力できまので大抵のテーマでは間に合うでしょう。これでいざACFとの離婚協議が始まっても、優位にたてるというわけです。
以下をfunctions.phpに書き込みます。
Advanced Custom Fieldsのタームメタ変更に合わせてwp_term_metaを上書き
add_filter( 'acf/update_value', 'acf_update_term_meta', 11, 3 );
function acf_update_term_meta($value, $post_id, $field) {
$term_id = intval(filter_var($post_id, FILTER_SANITIZE_NUMBER_INT));
if($term_id > 0) update_term_meta($term_id, $field['name'], $value);
return $value;
}
たとえばカテゴリに画像IDを登録したとして、それをテンプレートから出力する際は、以下様になります。wp_term_metaを呼び出す、つまりwpのデフォルト関数を使うだけ。シンプルですね。
$term_img = get_term_meta( $term_id, 'category_image', true );
if( $term_img ) {
echo wp_get_attachment_image ( $term_img, 'thumbnail' );
}