WordPressでフォームを使ったカスタムフィールド掛け合わせの検索機能実装と注意するポイント
最終更新日: Update!!
WordPressではサブクエリ機能を使って様々な条件で投稿データを取得することができます。今回はそんな中でもチェックボックスやテキスト入力などのインプット要素を使った条件絞り込みの検索フォームを使った投稿取得機能を実装する方法とその時の注意点をまとめてみたいと思います。
例えば、ネットショップや不動産物件などの検索機能によく使われていますね。WordPressにはカテゴリやタグ、タクソノミといった分類機能を基本として、投稿データに対して固有の情報を追加できるカスタムフィールドの情報も掛け合わせて検索されることもあります。今回は例として検索フォームと検索結果のリストが一緒になった検索結果ページを固定ページで、対象となる投稿とカスタム投稿で作成し、カスタムタクソノミとカスタムフィールドを検索条件に使いますので下記の通り準備します。
【検索結果一覧ページ設定】
・ページ:固定ページ(スラッグ:search-result)
・投稿:カスタム投稿(スラッグ:blog)
【検索条件用タクソノミ】
・タクソノミ1:カスタム分類(スラッグ:taxonomy_1)※複数選択
・タクソノミ2:カスタム分類(スラッグ:taxonomy_2)※単一選択
【検索条件用カスタムフィールド】
・複数選択(チェックボックス/metaキー:cf_checkbox)
・単一選択(ラジオボタン/metaキー:cf_radio)
・数値範囲指定(数値入力/metaキー:cf_number)
・除外テキスト指定(テキスト入力meta/キー:cf_text)
【その他検索用条件】
・キーワード検索
・並び順指定
またその他の検索条件としてキーワード検索や並び順の指定もできるようにしておきます。続いて検索条件のフォームと検索結果の出力部分をHTMLで作成していきます。form要素には、GETメソッドで検索結果ページを対象にし、GETパラメーターの値でクエリの条件が決まるようにしておきます。GETパラメーターを使った検索方法は過去記事「WordPressで独自のクエリパラメータを追加してGET値で記事を分類表示させる」にも詳しくまとめていますのでご参考に。
今回はWordPressでカスタムフィールドの掛け合わせで検索フォームを使った投稿検索機能を実装する例とその実装方法についてまとめてみました。WordPressの検索機能はとても充実しているので、ブログサイトに限らずいろんなものに対応することができますが、使い方によってはパフォーマンスに影響することもありますので、注意点も合わせて知っておきたいところですね。
複数の検索条件を指定できるフォームを使った投稿データの検索機能を実装する
項目が不確定になるタクソノミのタームや、カスタムフィールドの値は動的に出力されるようにあらかじめ変数に格納しておくと便利です。カスタムフィールドで扱う選択肢は「get_field_object()」を使って参照できます。実際に項目を使うときには「choices」キーで取得することができます。また値が配列型になるものについてはname属性に角カッコを入れて配列として受け取れるようにしておくのも忘れないようにしましょう。<form method="get" action="<?php echo esc_url(home_url())?>/search-result"> <?php $taxonomy_1_terms = get_terms('taxonomy_1'); $taxonomy_2_terms = get_terms('taxonomy_2'); $cf_checkbox_object = get_field_object('cf_checkbox'); $cf_radio_object = get_field_object('cf_radio'); ?> <table> <tbody> <tr> <th>タクソノミ1(複数選択)</th> <td> <?php foreach($taxonomy_1_terms as $term): ?> <label><input type="checkbox" name="<?php echo $term->taxonomy; ?>[]" value="<?php echo $term->slug; ?>"><?php echo $term->name; ?></label> <?php endforeach; ?> </td> </tr> <tr> <th>タクソノミ2(単一選択)</th> <td> <?php foreach($taxonomy_2_terms as $term): ?> <label><input type="radio" name="<?php echo $term->taxonomy; ?>" value="<?php echo $term->slug; ?>"><?php echo $term->name; ?></label> <?php endforeach; ?> </td> </tr> <tr> <th>カスタムフィールド(複数選択)</th> <td> <?php foreach($cf_checkbox_object['choices'] as $item): ?> <label><input type="checkbox" name="cf_checkbox[]" value="<?php echo $item; ?>"><?php echo $item; ?></label><br> <?php endforeach; ?> </td> </tr> <tr> <th>カスタムフィールド(単一選択)</th> <td> <?php foreach($cf_radio_object['choices'] as $item): ?> <label><input type="radio" name="cf_radio" value="<?php echo $item; ?>"><?php echo $item; ?></label><br> <?php endforeach; ?> </td> </tr> <tr> <th>カスタムフィールド(数値範囲指定)</th> <td> <label><input type="number" name="cf_number_min" value=""></label>〜<label><input type="number" name="cf_number_max" value=""></label> </td> </tr> <tr> <th>カスタムフィールド(除外テキスト指定)</th> <td> <label><input type="text" name="cf_text_exclusion" value=""></label> </td> </tr> <tr> <th>フリーキーワード</th> <td> <label><input type="search" name="search" value=""></label> </td> </tr> <tr> <th>並び順</th> <td> <select name="order"> <option value="DESC">降順</option><option value="ASC">昇順</option> </select> </td> </tr> </tbody> </table> <input type="submit" value="この条件で検査"> </form>数値入力は範囲指定なるので上限と下限を設定できる想定にしています。またカスタムフィールドのテキストキーワード指定は、除外する仕様で進めていきます。 続いてクエリの方をみていきます。今回はGETパラメーターでクエリの条件が変わるため、一部は動的に組む必要があります。GETパラメーターがない場合にはそのまま出力されるようまずはベースとなる固定のクエリを用意します。対象の投稿タイプやページネーション情報、ページあたりの出力数などあらかじめ固定で決めておきます。また後述するタクソノミやカスタムフィールドでのクエリは動的に変えられるため中身は空の状態で置いておきます。
$search_query = array( 'post_status' => 'publish', 'post_type' => 'blog', 'posts_per_page' => 10, 'paged' => get_query_var('paged') ? get_query_var('paged') : 1, 'tax_query' => array( 'relation' => 'AND', ), 'meta_query' => array( 'relation' => 'AND', ), );そして動的なクエリの方をみていきます。まずは完全なものの全体像をみていきます。これらは全ての条件が入力されている場合の結果になりますね。それぞれの値にはGETパラメーターの値が入るようにしておきます。今回はタクソノミについてはAND検索、カスタムフィールドについては単一及び複数選択肢はOR検索で、数値範囲指定とキーワード除外指定はAND検索になるように実装していきます。relationキーの部分で同階層の配列を対象にANDもしくはORの検索指定を行います。
'order' => $_GET['order'], 's' => $_GET['search'], 'tax_query' => array( 'relation' => 'AND', array( 'taxonomy' => 'taxonomy_1', 'field' => 'slug', 'terms' => $_GET['taxonomy_1'] ), array( 'taxonomy' => 'taxonomy_2', 'field' => 'slug', 'terms' => $_GET['taxonomy_2'] ) ), 'meta_query' => array( 'relation' => 'AND', array( 'relation' => 'OR', array( 'key' => 'cf_radio', 'value' => $_GET['cf_radio'], 'compare' => '=' ), array( 'key' => 'cf_checkbox', 'value' => カスタムフィールド複数選択の選択肢(foreachでループ出力), 'compare' => 'LIKE' ) ), array( 'key' => 'cf_text', 'value' => $_GET['cf_text'], 'compare' => 'NOT LIKE' ), array( 'relation' => 'AND', array( 'key' => 'cf_number', 'value' => $_GET['cf_number_min'], 'type' => 'NUMERIC', 'compare' => '>=' ), array( 'key' => 'cf_number', 'value' => $_GET['cf_number_max'], 'type' => 'NUMERIC', 'compare' => '<=' ) ) )上記を見るとわかりますが、複数検索のカスタムフィールドについては、値が複数になるのですが、データベース上にはシリアライズされた文字列として格納されるため、値を個別に取得してループ処理をして値ごとのクエリを動的に生成する必要があります。カスタムフィールドの値のシリアライズについては過去記事「WordPressのサブループで複数のカスタムフィールドの値を参照して記事を取得する」でも詳しくまとめていますのでそちらを参照に。 では動的部分のクエリをみていきます。基本的にはベースのクエリに対して、動的に生成されたクエリの配列を「array_push()」で結合していく形になります。最終的にはそれがサブクエリのクラスの引数として扱われ、その条件で記事を取得する形になります。ただし、GETパラメーターの有無でクエリを生成できるようにしておかないと想定したクエリの内容にならないので注意が必要です。下記のようにGETパラメータの値が空かどうかをチェックしながらクエリを動的に作成してきます。 クエリを配列としてマージするときにはマージする場所にも注意しましょう。正しい値の構成にならないと正常に機能しないのでデバッグしながら確認していきます。
$search_query = array( 'post_status' => 'publish', 'post_type' => 'blog', 'posts_per_page' => 10, 'paged' => get_query_var('paged') ? get_query_var('paged') : 1, 'tax_query' => array( 'relation' => 'AND', ), 'meta_query' => array( 'relation' => 'AND', ), ); // GETパラメータがある場合のクエリ変更 if(!empty($_GET)) { // 並び順 if(!empty($_GET['order'])) { $search_query = array_merge( $search_query, array( 'order' => $_GET['order'], ) ); } // キーワード検索 if(!empty($_GET['search'])) { $search_query = array_merge( $search_query, array( 's' => $_GET['search'], ) ); } // タクソノミ if(!empty($_GET['taxonomy_1'])) { $search_query['tax_query'] = array_merge( $search_query['tax_query'], array( array( 'taxonomy' => 'taxonomy_1', 'field' => 'slug', 'terms' => $_GET['taxonomy_1'] ) ) ); } if(!empty($_GET['taxonomy_2'])) { $search_query['tax_query'] = array_merge( $search_query['tax_query'], array( array( 'taxonomy' => 'taxonomy_2', 'field' => 'slug', 'terms' => $_GET['taxonomy_2'] ) ) ); } // キーワード除外 if(!empty($_GET['cf_text_exclusion'])) { $search_query['meta_query'] = array_merge( $search_query['meta_query'], array( array( 'key' => 'cf_text', 'value' => $_GET['cf_text_exclusion'], 'compare' => 'NOT LIKE' ) ) ); } // 数値範囲指定 if(!empty($_GET['cf_number_min']) && !empty($_GET['cf_number_max'])) { $search_query['meta_query'] = array_merge( $search_query['meta_query'], array( array( 'relation' => 'AND', array( 'key' => 'cf_number', 'value' => $_GET['cf_number_min'], 'type' => 'NUMERIC', 'compare' => '>=' ), array( 'key' => 'cf_number', 'value' => $_GET['cf_number_max'], 'type' => 'NUMERIC', 'compare' => '<=' ) ) ) ); } // カスタムフィールド選択肢 if(!empty($_GET['cf_radio']) && !empty($_GET['cf_checkbox'])) { // 複数選択&単一選択 $meta_query_select = array( array( 'relation' => 'OR', array( 'key' => 'cf_radio', 'value' => $_GET['cf_radio'], 'compare' => '=' ), ) ); foreach($_GET['cf_checkbox'] as $checkbox_value) { $meta_query_select[0] = array_merge( $meta_query_select[0], array( array( 'key' => 'cf_checkbox', 'value' => $checkbox_value, 'compare' => 'LIKE' ) ) ); } $search_query['meta_query'] = array_merge( $search_query['meta_query'], $meta_query_select ); } else if(!empty($_GET['cf_checkbox'])) { // 複数選択のみ $meta_query_select = array( array( 'relation' => 'OR', ) ); foreach($_GET['cf_checkbox'] as $checkbox_value) { $meta_query_select[0] = array_merge( $meta_query_select[0], array( array( 'key' => 'cf_checkbox', 'value' => $checkbox_value, 'compare' => 'LIKE' ) ) ); } $search_query['meta_query'] = array_merge( $search_query['meta_query'], $meta_query_select ); } else if(!empty($_GET['cf_radio'])) { // 単一選択のみ $search_query['meta_query'] = array_merge( $search_query['meta_query'], array( array( 'relation' => 'OR', array( 'key' => 'cf_radio', 'value' => $_GET['cf_radio'], 'compare' => '=' ), ) ) ); } } $search_posts = new WP_Query($search_query); // デバッグ echo '<pre>'; print_r($search_posts->query); echo '</pre>';先述の通り、チェックボックスの場合には個別の値を取得してクエリを作成する必要があるので、GETパラメータをループ処理で値を取得し、その値に合わせてそれぞれクエリを作成しながら結合先のクエリに合わせていきます。値はシリアライズ化されているため、LIKEでの条件で取得します。そしてもう一点、注意しておくポイントとして、AND検索の場合には初期値になるため、単純に配列を合わせていけばOKですが、OR検索の場合には新たに条件ごと1つの配列にまとめてあげる必要があります。 実際にカスタムフィールドのOR検索時のクエリの内容はこのようになります。ループ処理で個別に出力された値ごとにクエリが生成され、ORの条件で1つの配列にまとめられているのが確認できますね。上記の通りデバッグ用のコードを活用しながらチェックしていくと実装しやすいですね。
[meta_query] => Array ( [relation] => AND [0] => Array ( [relation] => OR [0] => Array ( [key] => cf_radio [value] => カスタムフィールド単一選択:選択肢1 [compare] => = ) [1] => Array ( [key] => cf_checkbox [value] => カスタムフィールド複数選択:選択肢1 [compare] => LIKE ) [2] => Array ( [key] => cf_checkbox [value] => カスタムフィールド複数選択:選択肢2 [compare] => LIKE ) [3] => Array ( [key] => cf_checkbox [value] => カスタムフィールド複数選択:選択肢3 [compare] => LIKE ) ) ..... 他のmeta_queryの条件 )これで検索フォームを使った検索機能が実装できましたが、カスタムフィールドを検索条件にする場合にはいくつか注意が必要になります。
その他カスタムフィールド掛け合わせの際に注意しておくポイント
カスタムフィールドはクエリ条件にできるものの、元々そういった前提を想定されていないため、使い方によってはどうしても問題が発生してくるケースもあります。特に押さえておきたいポイントをまとめてみました。 1. カスタムフィールドの選択肢ではLIKE検索になるため厳密な検索ができない 先述あるいは過去記事「WordPressのサブループで複数のカスタムフィールドの値を参照して記事を取得する」の通り、複数のカスタムフィールドの値はシリアライズされた文字列としてDBに格納されるため、個別の値として存在しないことになります。そのため、検索用の値でLIKE検索を行うのですが、これは指定した文字が含まれるかどうかのチェックになるので、別の値でも同じ文字列が含まれる場合には両方とも対象になってしまいます。 そのため、カスタムフィールドの複数の値を使った場合にはどうしても精度が低くなります。より厳密に検索したい場合にはタクソノミでの絞り込みを検討した方がよさそうですね。ただし、単一の文字列や数値などは完全一致での検索が可能になるのでこちらについては特に気にしなくても大丈夫ですね。 2. カスタムフィールドのOR検索は高負荷のため条件や投稿データ数によってかなり重たくなる カスタムフィールドの値を使った掛け合わせ検索で、ANDではなくOR検索の場合、条件によっては膨大な検索となるため、とても動作が重たくなるケースもあります。場合によってはサーバーがダウンするなどもあり得ますので、複雑な条件や投稿数が増える場合には特に注意しましょう。 できるだけAND検索になるような設計にするか、タクソノミを使った検索に切り替えること検討してみましょう。カスタムフィールドでの検索は特定の値だけで一致するような条件に押さえておきたいですね。 3. 並び順に複数の条件を掛け合わせる そのほかにもよくある用件として、並び順を複数条件で掛け合わせるものがあります。例えば今回のサンプルの場合、member-rankは高いものから優先的に並べて、member-idは若い順に並べるような仕様です。降順と昇順が混ざっているような形になりますね。'meta_query' => array( array( 'relation' => 'AND', 'member-rank' => array( 'key' => 'member-rank', 'type' => 'NUMERIC' ), 'sort-id' => array( 'key' => 'member-id', 'type' => 'NUMERIC' ), ), ), 'orderby' => array( 'member-rank' => 'DESC', 'member-id' => 'ASC' )そのためには、まずは「meta_query」でそれぞれのキーでAND検索になるように設定し、それらを「orderby」のクエリで先ほど指定したキーに対して、昇順や降順を個別に指定できるようになります。これで複数条件で昇順と降順がかけ合わさった並び順が実現できます。
今回はWordPressでカスタムフィールドの掛け合わせで検索フォームを使った投稿検索機能を実装する例とその実装方法についてまとめてみました。WordPressの検索機能はとても充実しているので、ブログサイトに限らずいろんなものに対応することができますが、使い方によってはパフォーマンスに影響することもありますので、注意点も合わせて知っておきたいところですね。
sponserd
keyword search
recent posts
- ViteでMarkuplintとPrettierを使える環境を構築する
ViteでMarkuplintとPrettierを使える環境を構築する
- ViteでStylelintとESlintを使える環境を構築する
ViteでStylelintとESlintを使える環境を構築する
- マウスオーバーしたセルを含む行列がハイライトするテーブルを作成する:has()擬似クラスの活用例
マウスオーバーしたセルを含む行列がハイライトするテーブルを作成する:has()擬似クラスの活用例
- ViteでVue.jsとVuex(Pinia)とVue Routerを使ってみる
ViteでVue.jsとVuex(Pinia)とVue Routerを使ってみる
- ViteでHandlebarsを使った複数ページの作成に使える外部JSONファイルのデータを読み込む
ViteでHandlebarsを使った複数ページの作成に使える外部JSONファイルのデータを読み込む
- ViteでTailwindCSSとテンプレートエンジンのHandlebarsを使ったページコーディング
ViteでTailwindCSSとテンプレートエンジンのHandlebarsを使ったページコーディング
- ViteでPostCSS周りの設定やSassを使う
ViteでPostCSS周りの設定やSassを使う
- フロントエンドの開発環境にVite + TypeScriptを導入する
フロントエンドの開発環境にVite + TypeScriptを導入する
categories