T-CREATOR

WordPress 技術アーキテクチャ図解:フック/ループ/クエリの全体像を一枚で理解

WordPress 技術アーキテクチャ図解:フック/ループ/クエリの全体像を一枚で理解

WordPress のカスタマイズや開発を行う際、フック、ループ、クエリという 3 つの重要な概念に出会うことになります。これらは WordPress の心臓部とも言える仕組みで、テーマやプラグインの開発には欠かせない要素です。

しかし、それぞれの役割や相互の関係性を正確に理解するのは、初めての方には難しいかもしれません。そこで本記事では、WordPress のアーキテクチャを図解を交えながら、わかりやすく解説していきますね。

背景

WordPress は世界中の Web サイトの 40% 以上で利用されているコンテンツ管理システム(CMS)です。その成功の秘訣は、プラグインやテーマによる拡張性の高さにあります。

この拡張性を支えているのが、フック(Hook)、ループ(Loop)、クエリ(Query)という 3 つの中核システムなのです。

WordPress の処理フロー全体像

まず、WordPress がリクエストを受けてから HTML を出力するまでの基本的な流れを見てみましょう。

mermaidflowchart TB
  request["HTTP リクエスト"] --> wp_load["WordPress 読み込み"]
  wp_load --> parse["URL 解析"]
  parse --> query_build["WP_Query 構築"]
  query_build --> db_query["データベースクエリ実行"]
  db_query --> loop_start["ループ開始"]
  loop_start --> template["テンプレート表示"]
  template --> hooks["フック実行"]
  hooks --> output["HTML 出力"]

この図が示すように、WordPress は URL を解析し、適切なデータをデータベースから取得(クエリ)、それをループで処理しながら、各所でフックを実行してカスタマイズポイントを提供しています。

3 つのコンポーネントの基本的な役割

各コンポーネントが担う役割を整理すると、次のようになります。

#コンポーネント主な役割代表的な API
1クエリデータベースから投稿やページなどのデータを取得するWP_Query, get_posts()
2ループ取得したデータを順番に処理するwhile (have_posts()), the_post()
3フック処理の途中で独自の処理を差し込むadd_action(), add_filter()

クエリでデータを取得し、ループで処理し、フックでカスタマイズする、という流れが WordPress 開発の基本パターンです。

課題

WordPress の 3 つのコンポーネントは、それぞれ独立して説明されることが多く、相互の関係性を理解しづらいという課題があります。

よくある誤解や混乱

初心者が陥りやすい誤解として、以下のようなものがあるでしょう。

  • クエリとループの区別がつかない:「ループを回す」と「データを取得する」を混同してしまう
  • フックのタイミングが不明:どのタイミングでフックが実行されるのかわからない
  • カスタムクエリの使い方:メインクエリとサブクエリの違いがわからない
  • グローバル変数への依存$post, $wp_query などのグローバル変数をいつ使うべきかわからない

これらの課題は、各コンポーネントが どのタイミングで どのように連携するか が見えないことに起因します。

アーキテクチャ理解の重要性

次の図は、3 つのコンポーネントがどのように相互作用するかを示しています。

mermaidflowchart LR
  subgraph query_phase["クエリフェーズ"]
    pre_query["pre_get_posts<br/>フック"] --> exec_query["SQL 実行"]
    exec_query --> posts_results["posts_results<br/>フック"]
  end

  subgraph loop_phase["ループフェーズ"]
    have_posts["have_posts()"] --> the_post["the_post()"]
    the_post --> setup_post["setup_postdata()"]
  end

  subgraph display_phase["表示フェーズ"]
    content_hook["the_content<br/>フィルター"] --> output_html["HTML 出力"]
  end

  query_phase --> loop_phase
  loop_phase --> display_phase

図で理解できる要点:

  • クエリ実行の前後にフックポイントがある
  • ループは取得済みデータを順次処理する
  • 表示時にもフィルターフックが働く

このように、フックはクエリやループの各段階に組み込まれており、WordPress の処理全体を柔軟にカスタマイズできる仕組みになっているのです。

解決策

WordPress のアーキテクチャを理解するには、3 つのコンポーネントを個別に理解した上で、それらがどのように連携するかを把握することが重要です。

クエリ(WP_Query)の仕組み

クエリは、WordPress におけるデータ取得の中心的な役割を担います。WP_Query クラスがその核となっていますね。

WP_Query の基本構造

WP_Query は、データベースからデータを取得するための強力な API です。

typescript// WP_Query の基本的な使い方
$args = array(
  'post_type' => 'post',      // 投稿タイプ
  'posts_per_page' => 10,     // 取得件数
  'orderby' => 'date',        // 並び順の基準
  'order' => 'DESC'           // 降順
);

// クエリオブジェクトの作成
$query = new WP_Query($args);

この $args パラメータによって、どのようなデータを取得するかを指定します。WordPress はこのパラメータを元に SQL クエリを構築し、データベースにアクセスするのです。

メインクエリとサブクエリ

WordPress には、ページ表示時に自動的に実行される「メインクエリ」と、開発者が明示的に作成する「サブクエリ」の 2 種類があります。

#クエリの種類実行タイミンググローバル変数用途
1メインクエリページロード時に自動実行$wp_queryページ本来のコンテンツ表示
2サブクエリ開発者が明示的に作成独自の変数追加コンテンツやウィジェット

メインクエリは WordPress が自動的に実行しますが、サブクエリは開発者が必要に応じて作成します。

typescript// サブクエリの例:最新の 5 件の投稿を取得
$recent_posts = new WP_Query(array(
  'posts_per_page' => 5,
  'orderby' => 'date',
  'order' => 'DESC'
));

サブクエリを使うことで、メインクエリとは独立した別のデータセットを取得できるのです。

ループ(The Loop)の仕組み

ループは、クエリで取得したデータを 1 件ずつ処理するための仕組みです。WordPress の基本中の基本と言えるでしょう。

標準的なループの構造

WordPress の標準的なループは、以下のような構造になっています。

typescript// クエリオブジェクトの作成
$query = new WP_Query($args);

// ループの開始判定
if ($query->have_posts()) {
  // 投稿がある限り繰り返す
  while ($query->have_posts()) {
    // 現在の投稿データをセットアップ
    $query->the_post();

    // テンプレートタグで投稿データを出力
    the_title();    // タイトル
    the_content();  // 本文
  }
}

このコードは、取得した投稿データを順番に処理していく基本パターンです。

ループの内部動作

ループの各メソッドが何をしているかを理解しましょう。

typescript// have_posts() - まだ未処理の投稿があるか確認
if ($query->have_posts()) {
  // 内部では current_post と post_count を比較している
  // return $this->current_post + 1 < $this->post_count;
}

have_posts() は、現在の位置(current_post)と総件数(post_count)を比較して、まだ処理すべき投稿が残っているかを判定しています。

typescript// the_post() - 次の投稿データをセットアップ
$query->the_post();

// 内部では以下の処理を実行
// 1. current_post をインクリメント
// 2. 次の投稿オブジェクトを取得
// 3. グローバル変数 $post にセット
// 4. setup_postdata() を呼び出し

the_post() を呼び出すと、次の投稿データがグローバル変数 $post にセットされ、the_title()the_content() などのテンプレートタグが使えるようになるのです。

ループのリセット

複数のクエリを使う場合、ループ終了後には必ずリセットを行いましょう。

typescript// サブクエリのループ
if ($query->have_posts()) {
  while ($query->have_posts()) {
    $query->the_post();
    // 処理
  }
}

// ループのリセット(重要)
wp_reset_postdata();

wp_reset_postdata() を呼び出すことで、グローバル変数 $post が元のメインクエリの状態に戻ります。これを忘れると、後続の処理で予期しない動作が発生する可能性がありますね。

フック(Hook)の仕組み

フックは、WordPress のコア処理に独自の処理を差し込むための仕組みです。プラグインやテーマのカスタマイズに不可欠な機能ですね。

アクションフックとフィルターフック

WordPress のフックには、2 つの種類があります。

#フックの種類役割戻り値代表例
1アクションフック特定のタイミングで処理を実行なしwp_head, wp_footer
2フィルターフックデータを加工・変換加工後の値the_content, the_title

アクションフックは「何かをする」、フィルターフックは「何かを変える」という違いがあります。

アクションフックの使い方

アクションフックを使って、WordPress の処理の途中で独自の処理を実行できます。

typescript// アクションフックの追加
add_action('wp_head', 'my_custom_head_script');

// 実行される関数
function my_custom_head_script() {
  echo '<script>console.log("Custom script loaded");</script>';
}

この例では、wp_head というタイミング(<head> タグ内の出力時)に、独自のスクリプトを挿入しています。

フィルターフックの使い方

フィルターフックを使って、WordPress が出力するデータを加工できます。

typescript// フィルターフックの追加
add_filter('the_content', 'add_reading_time');

// コンテンツを加工する関数
function add_reading_time($content) {
  // 文字数をカウント
  $word_count = str_word_count(strip_tags($content));

  // 読了時間を計算(1分あたり200語と仮定)
  $reading_time = ceil($word_count / 200);

  // コンテンツの先頭に読了時間を追加
  $time_message = '<p>読了時間: 約 ' . $reading_time . ' 分</p>';

  // 加工したコンテンツを返す
  return $time_message . $content;
}

この関数は、投稿コンテンツの先頭に推定読了時間を追加します。フィルターフックでは、必ず加工後の値を return で返すことが重要ですね。

クエリとフックの連携

クエリの実行前後には、多くのフックポイントが用意されています。

typescript// クエリ実行前のフック
add_action('pre_get_posts', 'modify_main_query');

function modify_main_query($query) {
  // メインクエリかつホームページの場合のみ
  if ($query->is_main_query() && $query->is_home()) {
    // 表示件数を変更
    $query->set('posts_per_page', 5);
  }
}

pre_get_posts フックを使うことで、クエリが実行される前にパラメータを変更できます。これにより、SQL が実行される前にクエリの条件を動的に変更できるのです。

3 つのコンポーネントの連携

クエリ、ループ、フックがどのように連携するかを、実践的な例で見てみましょう。

mermaidsequenceDiagram
  participant User as ユーザー
  participant WP as WordPress コア
  participant Query as WP_Query
  participant Hook as フックシステム
  participant DB as データベース
  participant Template as テンプレート

  User->>WP: ページリクエスト
  WP->>Query: クエリオブジェクト生成
  Query->>Hook: pre_get_posts 実行
  Hook-->>Query: クエリパラメータ変更
  Query->>DB: SQL 実行
  DB-->>Query: データ返却
  Query->>Hook: posts_results 実行
  Hook-->>Query: データ加工
  Query->>Template: ループ開始
  loop 各投稿
    Template->>Hook: the_title フィルター
    Template->>Hook: the_content フィルター
    Hook-->>Template: 加工済みデータ
  end
  Template->>User: HTML 出力

図で理解できる要点:

  • クエリ実行前に pre_get_posts でパラメータ変更可能
  • データ取得後に posts_results で結果を加工可能
  • ループ内では各テンプレートタグにフィルターが適用される

このシーケンス図が示すように、フックはクエリとループの各段階に組み込まれており、処理全体をきめ細かくカスタマイズできる仕組みになっています。

具体例

ここでは、実際の開発でよく使われるパターンを具体例として紹介します。

カスタムクエリとループの実装

サイドバーに「人気記事ランキング」を表示する例を見てみましょう。

ステップ 1:カスタムクエリの作成

まず、人気記事を取得するためのクエリを作成します。

typescript// 人気記事を取得するカスタムクエリ
$popular_posts_args = array(
  'post_type' => 'post',           // 投稿タイプ
  'posts_per_page' => 5,           // 5件取得
  'meta_key' => 'post_views_count', // カスタムフィールドのキー
  'orderby' => 'meta_value_num',   // 数値で並び替え
  'order' => 'DESC'                // 降順(多い順)
);

// WP_Query オブジェクトの生成
$popular_posts = new WP_Query($popular_posts_args);

このクエリは、post_views_count というカスタムフィールドの値(閲覧数)が多い順に投稿を取得します。

ステップ 2:ループでの表示

取得したデータをループで処理して表示します。

typescript// ループの開始
if ($popular_posts->have_posts()) {
  echo '<div class="popular-posts">';
  echo '<h3>人気記事ランキング</h3>';
  echo '<ul>';

  // 投稿を順番に処理
  while ($popular_posts->have_posts()) {
    $popular_posts->the_post();

ループの開始部分では、投稿があるかをチェックし、リスト表示のための HTML を出力します。

typescript    // 各投稿の情報を表示
    echo '<li>';
    echo '<a href="' . get_permalink() . '">';
    echo get_the_title();
    echo '</a>';
    echo '<span class="view-count">';
    echo get_post_meta(get_the_ID(), 'post_views_count', true);
    echo ' views</span>';
    echo '</li>';
  }

ループ内では、各投稿のタイトル、リンク、閲覧数を表示しています。

ステップ 3:ループのクリーンアップ

typescript  echo '</ul>';
  echo '</div>';
}

// グローバル変数をリセット
wp_reset_postdata();

最後に、グローバル変数 $post を元の状態に戻すために wp_reset_postdata() を呼び出します。これを忘れると、後続のメインループに影響が出る可能性がありますね。

フックを使ったカスタマイズ

次に、フックを使ってクエリの動作をカスタマイズする例を見てみましょう。

カテゴリーページの投稿順序変更

カテゴリーページでは、デフォルトでは新着順に投稿が表示されます。これをカスタムフィールドで並び替える例です。

typescript// pre_get_posts フックに関数を登録
add_action('pre_get_posts', 'custom_category_order');

function custom_category_order($query) {
  // メインクエリかつカテゴリーページの場合のみ
  if (!is_admin() && $query->is_main_query() && $query->is_category()) {

まず、管理画面ではなく、メインクエリで、かつカテゴリーページであることを確認します。

typescript    // 特定のカテゴリー(ID: 5)の場合のみ
    if ($query->get('cat') == 5) {
      // カスタムフィールドで並び替え
      $query->set('meta_key', 'priority');
      $query->set('orderby', 'meta_value_num');
      $query->set('order', 'ASC');
    }
  }
}

カテゴリー ID が 5 の場合、priority というカスタムフィールドの値で昇順に並び替えています。

コンテンツの自動加工

投稿本文の末尾に関連記事を自動的に追加する例です。

typescript// the_content フィルターに関数を登録
add_filter('the_content', 'add_related_posts');

function add_related_posts($content) {
  // 単一投稿ページの場合のみ
  if (is_single()) {

単一投稿ページ(個別記事のページ)でのみ処理を実行します。

typescript    // 現在の投稿のカテゴリーを取得
    $categories = get_the_category();

    if ($categories) {
      $category_ids = array();
      foreach ($categories as $category) {
        $category_ids[] = $category->term_id;
      }

現在の投稿が属するカテゴリーの ID を配列に格納します。

typescript      // 関連記事を取得するクエリ
      $related_args = array(
        'category__in' => $category_ids,  // 同じカテゴリー
        'post__not_in' => array(get_the_ID()), // 現在の記事を除外
        'posts_per_page' => 3,             // 3件取得
        'orderby' => 'rand'                // ランダム表示
      );

      $related_posts = new WP_Query($related_args);

同じカテゴリーに属する投稿から、現在の記事を除外して 3 件をランダムに取得します。

typescript      // 関連記事セクションの HTML を構築
      if ($related_posts->have_posts()) {
        $related_html = '<div class="related-posts">';
        $related_html .= '<h3>関連記事</h3>';
        $related_html .= '<ul>';

        while ($related_posts->have_posts()) {
          $related_posts->the_post();
          $related_html .= '<li><a href="' . get_permalink() . '">';
          $related_html .= get_the_title();
          $related_html .= '</a></li>';
        }

        $related_html .= '</ul>';
        $related_html .= '</div>';

        // グローバル変数をリセット
        wp_reset_postdata();

関連記事をループで処理し、HTML を構築します。ループ終了後は必ず wp_reset_postdata() を呼び出しますね。

typescript        // 元のコンテンツに関連記事を追加
        $content .= $related_html;
      }
    }
  }

  // 加工したコンテンツを返す
  return $content;
}

最後に、元のコンテンツに関連記事の HTML を追加して返します。フィルターフックでは、必ず値を返すことが重要です。

複雑なクエリとフックの組み合わせ

より実践的な例として、検索機能のカスタマイズを見てみましょう。

カスタムフィールドも含めた検索

WordPress のデフォルト検索では、タイトルと本文しか検索対象になりません。これをカスタムフィールドも含めるように拡張します。

typescript// posts_search フィルターに関数を登録
add_filter('posts_search', 'search_custom_fields', 10, 2);

function search_custom_fields($search, $query) {
  global $wpdb;

  // 検索クエリかつ管理画面以外
  if (!is_admin() && $query->is_main_query() && $query->is_search()) {
    $search_term = $query->get('s');

検索ページのメインクエリの場合のみ、処理を実行します。

typescript    if (!empty($search_term)) {
      // カスタムフィールドを検索対象に含める SQL を構築
      $custom_field_search = " OR (";
      $custom_field_search .= "EXISTS (";
      $custom_field_search .= "SELECT * FROM {$wpdb->postmeta} ";
      $custom_field_search .= "WHERE {$wpdb->postmeta}.post_id = {$wpdb->posts}.ID ";
      $custom_field_search .= "AND {$wpdb->postmeta}.meta_value LIKE '%{$search_term}%'";
      $custom_field_search .= ")";
      $custom_field_search .= ")";

      // 元の検索条件に追加
      $search = preg_replace(
        '/\)\s*\)\s*$/',
        $custom_field_search . '))',
        $search
      );
    }
  }

  return $search;
}

この関数は、WordPress が生成する SQL の WHERE 句を書き換えて、カスタムフィールドも検索対象に含めています。

アーキテクチャの全体像

これまで見てきた 3 つのコンポーネントの関係を、最終的な全体図としてまとめます。

mermaidflowchart TB
  subgraph request_phase["リクエスト処理"]
    http_request["HTTP リクエスト"] --> wp_load["WordPress 初期化"]
    wp_load --> parse_request["リクエスト解析"]
  end

  subgraph query_phase["クエリ構築"]
    parse_request --> create_query["WP_Query 生成"]
    create_query --> hook_pre["pre_get_posts<br/>アクションフック"]
    hook_pre --> build_sql["SQL 構築"]
    build_sql --> execute_sql["DB クエリ実行"]
    execute_sql --> hook_posts["posts_results<br/>フィルターフック"]
  end

  subgraph loop_phase["ループ処理"]
    hook_posts --> check_posts["have_posts() 判定"]
    check_posts --> the_post_call["the_post() 呼び出し"]
    the_post_call --> setup_data["setup_postdata()"]
  end

  subgraph template_phase["テンプレート表示"]
    setup_data --> template_tags["テンプレートタグ"]
    template_tags --> hook_title["the_title<br/>フィルターフック"]
    template_tags --> hook_content["the_content<br/>フィルターフック"]
    hook_title --> render["HTML レンダリング"]
    hook_content --> render
  end

  subgraph output_phase["出力"]
    render --> hook_footer["wp_footer<br/>アクションフック"]
    hook_footer --> output_html["HTML 出力"]
  end

  loop_phase --> |次の投稿| loop_phase
  loop_phase --> |全投稿処理完了| template_phase

図で理解できる要点:

  • リクエストからクエリ、ループ、テンプレート、出力まで一連の流れがある
  • 各段階にフックポイントが配置されている
  • ループは繰り返し処理され、すべての投稿を順次処理する

この全体像を理解することで、WordPress のどの段階でどのようなカスタマイズが可能かが明確になります。

まとめ

WordPress の技術アーキテクチャは、クエリ、ループ、フックという 3 つの中核システムで構成されています。

クエリ(WP_Query)はデータベースからデータを取得し、ループ(The Loop)はそのデータを順番に処理し、フック(Hook)は各段階で独自の処理を差し込むことができます。これらが有機的に連携することで、WordPress の高い拡張性が実現されているのです。

各コンポーネントの役割と連携を理解することで、より効果的なテーマやプラグインの開発が可能になるでしょう。

特に重要なポイントは以下の通りです。

  • クエリpre_get_posts フックでクエリ実行前にパラメータを変更できる
  • ループwp_reset_postdata() でグローバル変数を必ずリセットする
  • フック:アクションフックは処理を追加、フィルターフックはデータを加工
  • 連携:3 つのコンポーネントが段階的に実行され、各段階にフックポイントがある

WordPress 開発では、これらの仕組みを理解し、適切に活用することが成功への鍵となります。本記事の図解と具体例が、皆さんの WordPress 開発の一助となれば幸いです。

関連リンク