タイトル: PHPの使用方法
SEOタイトル: WordPress テーマで PHP を使う方法(functions.php / WP_Query / フック / 子テーマ)
| この記事の要点 |
|
WordPress テーマと PHP
WordPress のテーマは PHP ファイルの集合体です。テンプレート階層 (Template Hierarchy) に従って、表示されるページに対応する PHP が選ばれて実行されます。
| ページ | 使われる PHP(優先順) |
|---|---|
| トップ(投稿一覧) | home.php → index.php |
| 個別投稿 | single-{post_type}.php → single.php → singular.php → index.php |
| 固定ページ | page-{slug}.php → page-{id}.php → page.php |
| カテゴリーアーカイブ | category-{slug}.php → category.php → archive.php |
| 404 | 404.php → index.php |
| 検索結果 | search.php → index.php |
functions.php でフックを使う
WordPress は「アクション (action)」と「フィルター (filter)」のフックで動作を拡張します:
自動出力
add_theme_support('html5', ['search-form', 'comment-form', 'gallery']);
register_nav_menus([
'primary' => 'メインメニュー',
'footer' => 'フッターメニュー',
]);
});
// フィルター: 値を加工して返す
add_filter('the_title', function ($title, $post_id) {
if (is_admin()) return $title;
return '★ ' . $title;
}, 10, 2);
add_filter('excerpt_more', function () {
return ' ... 続きを読む';
});
// 投稿保存時に何かしたい
add_action('save_post', function ($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
// 何か処理(キャッシュ削除など)
});
テンプレートでの The Loop
WordPress テンプレートの中核は「The Loop」と呼ばれる投稿の繰り返し処理です:
>
投稿が見つかりません
WP_Query / get_posts でカスタムクエリ
'post',
'posts_per_page' => 5,
'category_name' => 'news',
'orderby' => 'date',
'order' => 'DESC',
]);
if ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
// The Loop の中
echo '' . get_the_title() . '
';
}
wp_reset_postdata(); // ★ 必須: グローバル $post を戻す
}
// より簡潔に get_posts(自動リセット不要だが少し低機能)
$posts = get_posts([
'numberposts' => 3,
'category' => 5,
'meta_key' => 'featured',
'meta_value' => 'yes',
]);
foreach ($posts as $p) {
setup_postdata($GLOBALS['post'] = $p);
echo get_the_title($p);
}
wp_reset_postdata();
カスタム投稿タイプ (Custom Post Type)
add_action('init', function () {
register_post_type('product', [
'label' => '商品',
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-cart',
'supports' => ['title', 'editor', 'thumbnail', 'custom-fields'],
'rewrite' => ['slug' => 'products'],
'show_in_rest' => true, // Gutenberg / REST API 対応
]);
register_taxonomy('product_category', 'product', [
'label' => '商品カテゴリ',
'hierarchical' => true, // カテゴリ風(false ならタグ風)
'show_in_rest' => true,
]);
});
// アーカイブテンプレート: archive-product.php
// 個別テンプレート: single-product.php
ACF (Advanced Custom Fields)
ACF プラグインを入れると、管理画面でカスタムフィールド(任意のキー・型)を設計でき、テーマからは関数で値を呼べます:
// テキストフィールド
$price = get_field('price');
echo esc_html($price);
// 画像フィールド(return format = Image Array)
$img = get_field('hero_image');
if ($img) {
echo '
';
}
// 繰り返しフィールド
if (have_rows('faqs')) {
echo '';
while (have_rows('faqs')) {
the_row();
echo '- ' . esc_html(get_sub_field('question')) . '
';
echo '- ' . wp_kses_post(get_sub_field('answer')) . '
';
}
echo '
';
}
JS / CSS の読み込み (wp_enqueue)
テンプレートに直接 や を書くのは非推奨。WordPress の依存解決機構を活かすため wp_enqueue_script / wp_enqueue_style を使います。
add_action('wp_enqueue_scripts', function () {
// CSS
wp_enqueue_style(
'theme-main',
get_stylesheet_uri(),
[],
wp_get_theme()->get('Version') // バージョン (キャッシュ対策)
);
// JS(footer に出力、jQuery 依存)
wp_enqueue_script(
'theme-main',
get_template_directory_uri() . '/js/main.js',
['jquery'],
'1.0.0',
true // footer 読み込み
);
// JS に PHP からデータ受け渡し
wp_localize_script('theme-main', 'ThemeData', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('theme_nonce'),
]);
});
ショートコード (Shortcode)
add_shortcode('hello', function ($atts, $content = null) {
$atts = shortcode_atts(['name' => 'Guest'], $atts);
return 'こんにちは、' . esc_html($atts['name']) . 'さん!'
. do_shortcode($content) . '';
});
// 投稿本文中で [hello name="太郎"]中身[/hello]
子テーマ (Child Theme)
親テーマをそのまま改造するとアップデートで上書きされます。子テーマを作って差分だけ書きます:
// wp-content/themes/twentytwentyfour-child/style.css
/*
Theme Name: Twenty Twenty-Four Child
Template: twentytwentyfour
Version: 1.0.0
*/
// functions.php
add_action('wp_enqueue_scripts', function () {
wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css');
wp_enqueue_style('child-style',
get_stylesheet_directory_uri() . '/style.css',
['parent-style'],
wp_get_theme()->get('Version')
);
});
// 親と同じ名前のテンプレ (例: single.php) を置けば子が優先される
デバッグ: WP_DEBUG
// wp-config.php
define('WP_DEBUG', true); // エラー表示有効
define('WP_DEBUG_LOG', true); // wp-content/debug.log に出力
define('WP_DEBUG_DISPLAY', false); // 画面には出さない(本番準拠)
define('SCRIPT_DEBUG', true); // 非ミニファイ版 JS/CSS
// テーマからログ
error_log('値: ' . print_r($value, true));
セキュリティ: エスケープ / Nonce
// 出力時のエスケープ(必須)
echo esc_html($user_input); // HTML 用
echo esc_attr($attr); // 属性用
echo esc_url($link); // URL 用
echo wp_kses_post($html_content); // 投稿本文相当の HTML を許可
// CSRF (Nonce)
$nonce = wp_create_nonce('my_action');
// フォーム内に
if (!wp_verify_nonce($_POST['_wpnonce'] ?? '', 'my_action')) {
wp_die('不正なリクエスト');
}
// SQL(プレースホルダ)
global $wpdb;
$wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE post_status = %s LIMIT %d",
'publish', 10
));
FAQ
Q: functions.php と プラグインの違い
A: 機能がテーマ固有(見た目寄り)なら functions.php、サイト共通機能(CPT 等)はプラグイン化推奨。テーマ変更しても残せます。
Q: Gutenberg ブロックを PHP で作りたい
A: register_block_type + render_callback。ブロックエディタ側は JS 必要ですが「ダイナミックブロック」はサーバ側 PHP でレンダリングします。
Q: WP_Query と get_posts どっちを使う?
A: 機能差はあまり無く、WP_Query はオブジェクト指向で柔軟、get_posts は配列で受け取れる手軽さ。複雑なクエリやページング使うなら WP_Query。