4.

WordPress テーマで PHP を使う方法(functions.php / WP_Query / フック / 子テーマ)

編集
この記事の要点
  • functions.php でテーマに機能追加。add_action / add_filter で WP のフックに処理を挿入
  • Template Hierarchy: index.php / single.php / page-{slug}.php など命名規則で読まれる PHP が決まる
  • WP_Query / get_posts で投稿を取得(カスタムクエリ)。Loop の中で the_title() / the_content()
  • カスタム投稿タイプ (CPT) + ACF (Advanced Custom Fields) でブログ以外の構造化コンテンツも管理
  • wp_enqueue_script / style で JS/CSS を読み込み(直接 <script> は非推奨)
  • 子テーマで親テーマをアップデート可能なまま改造、WP_DEBUG でエラー検出

WordPress テーマと PHP

WordPress のテーマは PHP ファイルの集合体です。テンプレート階層 (Template Hierarchy) に従って、表示されるページに対応する PHP が選ばれて実行されます。

ページ使われる PHP(優先順)
トップ(投稿一覧)home.phpindex.php
個別投稿single-{post_type}.phpsingle.phpsingular.phpindex.php
固定ページpage-{slug}.phppage-{id}.phppage.php
カテゴリーアーカイブcategory-{slug}.phpcategory.phparchive.php
404404.phpindex.php
検索結果search.phpindex.php

functions.php でフックを使う

WordPress は「アクション (action)」と「フィルター (filter)」のフックで動作を拡張します:

<?php
// テーマの functions.php

// アクション: 何かの「タイミング」で処理を実行
add_action('after_setup_theme', function () {
    // テーマがセットアップされた直後
    add_theme_support('post-thumbnails');       // アイキャッチ画像
    add_theme_support('title-tag');             // <title> 自動出力
    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 ' ... <a href="' . get_permalink() . '">続きを読む</a>';
});

// 投稿保存時に何かしたい
add_action('save_post', function ($post_id) {
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    // 何か処理(キャッシュ削除など)
});

テンプレートでの The Loop

WordPress テンプレートの中核は「The Loop」と呼ばれる投稿の繰り返し処理です:

<?php
// single.php の例
get_header();
?>

<main>
    <?php if (have_posts()) : ?>
        <?php while (have_posts()) : the_post(); ?>

            <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
                <h1><?php the_title(); ?></h1>

                <div class="meta">
                    <time datetime="<?php the_time('c'); ?>">
                        <?php the_time(get_option('date_format')); ?>
                    </time>
                    by <?php the_author(); ?>
                </div>

                <?php if (has_post_thumbnail()) : ?>
                    <?php the_post_thumbnail('large'); ?>
                <?php endif; ?>

                <div class="content"><?php the_content(); ?></div>
            </article>

            <?php comments_template(); ?>

        <?php endwhile; ?>
    <?php else : ?>
        <p>投稿が見つかりません</p>
    <?php endif; ?>
</main>

<?php
get_sidebar();
get_footer();

WP_Query / get_posts でカスタムクエリ

<?php
// 特定カテゴリの最新 5 件
$query = new WP_Query([
    'post_type'      => '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 '<h3><a href="' . get_permalink() . '">' . get_the_title() . '</a></h3>';
    }
    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 '<img src="' . esc_url($img['url']) . '" alt="' . esc_attr($img['alt']) . '">';
}

// 繰り返しフィールド
if (have_rows('faqs')) {
    echo '<dl class="faq">';
    while (have_rows('faqs')) {
        the_row();
        echo '<dt>' . esc_html(get_sub_field('question')) . '</dt>';
        echo '<dd>' . wp_kses_post(get_sub_field('answer')) . '</dd>';
    }
    echo '</dl>';
}

JS / CSS の読み込み (wp_enqueue)

テンプレートに直接 <script><link> を書くのは非推奨。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 '<div class="hello">こんにちは、' . esc_html($atts['name']) . 'さん!'
         . do_shortcode($content) . '</div>';
});

// 投稿本文中で [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');
// フォーム内に <?php wp_nonce_field('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。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. インストール方法(Ubuntu + Apache)
  2. Wordpressのインストール方法
  3. ローカル環境への導入方法
  4. PHPの使用方法
  5. DB設定
  6. DB接続方法
  7. index.phpをURLに含めないようにする方法
  8. コメントに絵文字を入力可能にする方法
  9. サイト(ドメイン)の引越し方法
  10. 独自の関数一覧
  11. エラー一覧
  12. Wordpressの一連の処理の流れ

最近更新/作成されたページ