カスタム投稿タイプ(CPT)が増えると、archive-xxx.php
が雪だるま式に増え、管理が煩雑になりがちです。アーカイブルーターを導入すると、入口は常に archive.php
の1枚だけに集約し、投稿タイプごとに別置きテンプレートへ安全に振り分けられます。この記事では、「require_once
を1か所(functions.php
)にのみ書く」運用に整理した、コピペで使える完全版コードを提示します。
投稿タイプ名とテンプレート名はダミー(例:cpt_news
, cpt_event
, cpt_book
)を使用しています。必要に応じて差し替えてください。
導入の狙いとメリット
- ファイルの整理:
archive-xxx.php
の乱立を防ぎ、archive.php
を1枚に集約。専用テンプレはtemplates/archive/
にまとめる。 - 拡張が容易:新規CPT追加時は「ルーターマップに1行追加+テンプレ1枚」だけ。
- 安全なフォールバック:マップ未定義やテンプレ欠落時は
archive.php
内の標準ループをそのまま実行。 - テーマ階層に非依存:
locate_template()
により子テーマ優先・親テーマfallbackで安全に解決。
ディレクトリ構成(例)
my-theme/
├─ functions.php ← ここで router を一度だけ require_once
├─ archive.php ← 入口は常にこれ1枚(require は書かない)
├─ functions/
│ └─ archive-router.php ← ルーター本体(マッピング定義)
└─ templates/
└─ archive/
├─ news.php ← cpt_news 用ダミーテンプレ
├─ event.php ← cpt_event 用ダミーテンプレ
└─ book.php ← cpt_book 用ダミーテンプレ
1) functions.php(ここで一度だけ読み込む)
<?php
/**
* functions.php
*
* テーマ起動時に一度だけルーター本体を読み込む。
* ※ archive.php には require_once を書かない運用に統一。
*/
// 他の require があるならそのまま…
require_once get_theme_file_path('functions/archive-router.php');
// 以降、my_get_archive_template_for_query() / my_include_archive_template() を全体で利用可能。
2) ルーター本体(functions/archive-router.php)
<?php
/**
* Archive Router (アーカイブルーター)
*
* 目的:
* - archive.php を 1 枚に集約しつつ、投稿タイプ(CPT)ごとに別置きテンプレへルーティング。
* - マッピング不一致時は null を返し、archive.php 側の標準ループを実行(安全なフォールバック)。
*
* 設置例:
* /wp-content/themes/my-theme/functions/archive-router.php
*
* 読み込み(必ず 1 回だけ):
* functions.php で:
* require_once get_theme_file_path('functions/archive-router.php');
*/
if (!function_exists('my_get_archive_template_for_query')) {
/**
* 現在のクエリに対応するテンプレート(テーマ相対パス)を返す。
* 見つからなければ null(=archive.php 内のデフォルト表示へ)。
*
* 優先:
* 1) 投稿タイプアーカイブの post_type
* 2) タクソノミーアーカイブの object_type 先頭
*
* @return string|null
*/
function my_get_archive_template_for_query(): ?string
{
// --- 1) 投稿タイプを特定 ---
$post_type = get_query_var('post_type');
// is_post_type_archive() の時に配列になる可能性を吸収
if (is_array($post_type)) {
$post_type = $post_type[0] ?? '';
}
// 念のためクエリ済みオブジェクトからも補完
if (!$post_type && is_post_type_archive()) {
$qo = get_queried_object();
if (!empty($qo->name)) {
$post_type = $qo->name;
}
}
// --- 2) タクソノミーアーカイブ: 紐づく投稿タイプを推定 ---
if (!$post_type && is_tax()) {
$tax_obj = get_queried_object();
if ($tax_obj && !empty($tax_obj->taxonomy)) {
$tax = get_taxonomy($tax_obj->taxonomy);
if ($tax && !empty($tax->object_type) && is_array($tax->object_type)) {
// 複数紐づく場合は先頭を採用(必要なら filter で上書き)
$post_type = $tax->object_type[0];
}
}
}
// --- 3) 投稿タイプ → テンプレートのマップ ---
// ここを編集/追加するだけで拡張可能。ダミー名を使用しています。
$map = [
'cpt_news' => 'templates/archive/news.php',
'cpt_event' => 'templates/archive/event.php',
'cpt_book' => 'templates/archive/book.php',
];
/**
* 外部からマップを追加・上書き可能にするフック。
* 例:
* add_filter('my/archive_template_map', function($map){
* $map['cpt_movie'] = 'templates/archive/movie.php';
* return $map;
* });
*/
$map = apply_filters('my/archive_template_map', $map);
// --- 4) 解決 ---
if ($post_type && isset($map[$post_type])) {
return $map[$post_type];
}
// 見つからない → null(archive.php の標準表示へ)
return null;
}
}
if (!function_exists('my_include_archive_template')) {
/**
* 相対パスで指定されたテンプレートを読み込む。
* 子テーマ → 親テーマの順で探索。
*
* @param string $relative_path テーマ相対パス
* @return bool 読み込めたら true / 見つからなければ false
*/
function my_include_archive_template(string $relative_path): bool
{
// locate_template は子→親の順に探索する
$full = locate_template($relative_path);
if (!$full) {
// 念のため親テーマ直指定もトライ
$candidate = get_theme_file_path($relative_path);
if (file_exists($candidate)) {
$full = $candidate;
}
}
if ($full && file_exists($full)) {
require $full;
return true;
}
// 見つからない場合は false を返す(archive.php 側でフォールバック)
return false;
}
}
3) archive.php(ルーティング+フォールバック)
<?php
/**
* archive.php
*
* 役割:
* 1) 投稿タイプに対応する別置きテンプレートを解決し、存在すればそれを読み込んで終了。
* 2) 未定義・不在なら、このファイル内の標準ループ(デフォルト表示)を実行。
*
* 注意:
* - ルーターの require_once は functions.php で一度だけ行う前提。
* - ここには require_once を書かない(冗長さ・重複を避ける)。
*/
// ルーティング: 見つかれば専用テンプレで完結
if (function_exists('my_get_archive_template_for_query') && function_exists('my_include_archive_template')) {
if ($template_rel = my_get_archive_template_for_query()) {
if (my_include_archive_template($template_rel)) {
return; // ここで処理終了(下のデフォルト描画は実行しない)
}
}
}
// --- ここからデフォルト表示(= マップ未定義/テンプレ欠落時のフォールバック) ---
get_header();
?>
<main class="l-container">
<div class="l-content">
<header class="c-archive-header">
<h1 class="c-archive-title">
<?php
if (is_post_type_archive()) {
post_type_archive_title(); // CPT名など
} elseif (is_category() || is_tag() || is_tax()) {
single_term_title(); // ターム名
} else {
echo esc_html__('Archive', 'my-textdomain');
}
?>
</h1>
<?php if (term_description()) : ?>
<div class="c-archive-desc"><?php echo wp_kses_post(term_description()); ?></div>
<?php endif; ?>
</header>
<?php if (have_posts()) : ?>
<ul class="c-archive-list">
<?php while (have_posts()) : the_post(); ?>
<li class="c-archive-item">
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
<time datetime="<?php echo esc_attr(get_the_date('c')); ?>">
<?php echo esc_html(get_the_date()); ?>
</time>
</li>
<?php endwhile; ?>
</ul>
<nav class="c-archive-pager">
<?php the_posts_pagination([
'prev_text' => '«',
'next_text' => '»',
]); ?>
</nav>
<?php else : ?>
<p>投稿はありません。</p>
<?php endif; ?>
</div>
</main>
<?php get_footer(); ?>
4) ダミーの専用テンプレ例(templates/archive/news.php)
実運用ではデザインやループ構造を自由にカスタマイズしてください。
下記はcpt_news
の例。event.php
やbook.php
も同様に複製して流用できます。
<?php
/**
* templates/archive/news.php
*
* 投稿タイプ: cpt_news 用のアーカイブテンプレート(ダミー)。
* デザイン/メタ/ソートなどは自由に作り込む。
*/
get_header();
// クエリに設定された投稿タイプ(配列吸収)
$post_type = get_query_var('post_type');
if (is_array($post_type)) { $post_type = $post_type[0] ?? ''; }
// 表示タイトル: 投稿タイプラベルが取れればそれを使う
$cpt_obj = $post_type ? get_post_type_object($post_type) : null;
$title = ($cpt_obj && !empty($cpt_obj->labels->name)) ? $cpt_obj->labels->name : 'News';
?>
<main class="l-container">
<div class="l-content">
<header class="c-archive-header">
<h1 class="c-archive-title"><?php echo esc_html($title); ?></h1>
<?php if (is_tax()) : ?>
<p class="c-archive-tax">Filter: <?php echo esc_html(single_term_title('', false)); ?></p>
<?php endif; ?>
</header>
<?php if (have_posts()) : ?>
<div class="c-card-grid">
<?php while (have_posts()) : the_post(); ?>
<article class="c-card">
<a href="<?php the_permalink(); ?>" class="c-card__link">
<h2 class="c-card__title"><?php the_title(); ?></h2>
<div class="c-card__meta">
<time datetime="<?php echo esc_attr(get_the_date('c')); ?>">
<?php echo esc_html(get_the_date()); ?>
</time>
<?php
// 例: カスタムフィールドやタクソノミー表示を追加
// $source = get_post_meta(get_the_ID(), 'news_source', true);
// if ($source) echo '<span class="c-card__badge">'.esc_html($source).'</span>';
?>
</div>
<p class="c-card__excerpt"><?php echo esc_html(wp_trim_words(get_the_excerpt(), 40)); ?></p>
</a>
</article>
<?php endwhile; ?>
</div>
<nav class="c-archive-pager">
<?php the_posts_pagination([
'prev_text' => '«',
'next_text' => '»',
]); ?>
</nav>
<?php else : ?>
<p>該当する記事はありません。</p>
<?php endif; ?>
</div>
</main>
<?php get_footer(); ?>
5) ルーティング・マップの外部拡張(任意)
複数のプラグインや子テーマからマップを安全に足し込むなら、フックを使います。
<?php
// 例: 子テーマの functions.php で
add_filter('my/archive_template_map', function(array $map): array {
// 追加: 新しい投稿タイプ cpt_movie を別テンプレへ
$map['cpt_movie'] = 'templates/archive/movie.php';
// 既存上書き: cpt_news のテンプレ先を差し替え
// $map['cpt_news'] = 'templates/archive/news-alt.php';
return $map;
});
導入手順(最短)
functions/archive-router.php
を作成。functions.php
で 一度だけrequire_once get_theme_file_path('functions/archive-router.php');
を記述。archive.php
を本記事の内容に差し替える(※ここにはrequire_once
を書かない)。templates/archive/
に、CPTごとのテンプレ(必要分のみ)を作成。- ルーターマップに対象の投稿タイプを追加。
運用のコツ
- 最初はフォールバックでOK:専用テンプレが不要なら
$map
に登録せず、archive.php
の標準ループに任せる。必要になった時点でテンプレとマップを追加。 - 命名と置き場所を統一:
templates/archive/<post-type>.php
に揃えると検索と保守が楽。 - 子テーマ対応:
locate_template()
で子テーマが優先されるため、親テーマを変更せず差し替え可能。
コメント