0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WordPressを"フレームワーク"として考えてみる ― 最小テーマ自作で見えてくる設計の裏側

0
Last updated at Posted at 2026-04-16

はじめに

こんにちは!株式会社BTM 札幌ラボの齊藤です!

WordPressは、CMS(コンテンツ・マネジメント・システム)の一つで
2003年にブログツールとしてスタートし、
Web全体の約43%を占めるまでに成長しました(W3Techs調べ)。

WordPressの大きな特徴の1つに、
コアを触らずに機能を拡張できる構造があります。
テーマとプラグインによる拡張の仕組みです。

この構造を掘り下げてみると、
一般的なフレームワークと共通する設計パターンが多く見えてきます。

本記事では、最小構成のテーマをゼロから自作しながら、
WordPressの仕組みをフレームワークの概念と対比して読み解いていきます。

WordPressを触ったことのあるエンジニアは多いと思いますが、
「なぜコアファイルを触ってはいけないのか」
「なぜテーマファイルの名前に規則があるのか」
を改めて意識する機会は少ないかもしれません。

「なんとなく使っていたWordPress」の裏側にある
設計の意図が見えてくると面白いかもしれません。

WordPressはなぜ「拡張前提」の構造になったのか

WordPressの拡張性は、最初から備わっていたわけではありません。
初期のWordPressでは、カスタマイズにはコアコードの直接変更が必要でした。

転機は2004〜2005年にかけて訪れます。

バージョン 時期 導入されたもの
v1.2(Mingus) 2004年5月 プラグインアーキテクチャ(コアを変更せず機能追加が可能に)
v1.5(Strayhorn) 2005年2月 テーマシステム(デザインの切り替えが可能に)
v2.0(Duke) 2005年12月 functions.php(テーマごとのカスタマイズロジック集約)

わずか2年弱で、「コアに触らず、テーマとプラグインで拡張する」
という現在のWordPressの基本構造が出来上がりました。

注目すべきは、この時期にブログプラットフォームの競合であったMovable Typeが
ライセンス条件の変更でユーザーの不満を招いていたことです。
WordPressはオープンソースかつ拡張しやすい構造を武器に、
多くのユーザーを取り込むことに成功しました。

つまり、WordPressの拡張前提の設計は
「誰でも自由にカスタマイズできること」を生存戦略とした結果といえます。

この構造を、フレームワークの概念と対比しながら見ていきましょう。

対象読者

  • WordPressをなんとなく触っているが、仕組みをちゃんと理解したい方
  • Laravel等のフレームワーク経験はあるが、WordPressに馴染みの薄い方

前提

  • WordPressがローカル環境で動作していること
  • PHPの基本文法がわかること
筆者の環境:Docker使用の仮想環境にて構築
テーマファイルの配置先はコンテナ内の /var/www/html/wp-content/themes/ 配下

本記事で作るもの

以下の7ファイルで構成される最小テーマを作ります。

wp-content/themes/my-custom-theme/
├── style.css            ← テーマ定義
├── functions.php        ← カスタマイズロジックの集約
├── front-page.php       ← トップページ
├── single.php           ← 投稿詳細ページ
├── header.php           ← 共通ヘッダー
├── footer.php           ← 共通フッター
└── index.php            ← フォールバック

1. テーマ定義 ― style.css

WordPressにテーマを認識させるのに、設定ファイルへの登録は不要です。
必要なのは、決まった場所に決まった名前のファイルを置くことだけ。
まずはそれを体験してみましょう。

/*
Theme Name: My Custom Theme
Description: フレームワークとしてのWordPressを理解するための最小テーマ
Version: 1.0
*/

/* 以下、最低限のスタイル */
body {
    font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN', sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f5f5f5;
    color: #333;
}

.site-header {
    background-color: #0073aa;
    color: #fff;
    padding: 20px;
}

.site-header a {
    color: #fff;
    text-decoration: none;
}

.site-content {
    max-width: 800px;
    margin: 20px auto;
    padding: 20px;
    background-color: #fff;
}

.site-footer {
    text-align: center;
    padding: 20px;
    color: #666;
    font-size: 0.9em;
}

.post-card {
    border: 1px solid #ddd;
    padding: 15px;
    margin-bottom: 15px;
    background-color: #fff;
}

.post-card h2 a {
    color: #0073aa;
    text-decoration: none;
}

Theme Name のコメントヘッダーがあることで、
WordPress管理画面の「外観」→「テーマ」に表示されるようになります。

ここで1つポイントなのは、
WordPressはファイルの「名前」と「場所」と「コメントの記述」によって自動的に認識するという点です。
設定ファイルに「このテーマを使う」と明示的に登録する必要はありません。
これは、フレームワークの世界でよく言われる Convention over Configuration(設定より規約) の考え方に通じます。
WordPressのテーマ認識の仕組みも、同じパターンに沿っていると言えます。

2. テンプレート階層 = ルーティング

ファイルを1つ置くだけで、そのページのルーティングが完成する。
WordPressにはルーティング設定ファイルが存在しません。
ではどうやってURLとテンプレートを対応づけているのか?

フレームワークのルーティングとは

一般的なフレームワークでは、「このURLにアクセスしたら、この処理を実行する」
という対応をルーティング設定ファイルに記述します。

例:Laravelの場合
Route::get('/posts/{id}', [PostController::class, 'show']);

例:Djangoの場合
path('posts/<int:id>/', views.post_detail)

WordPressのルーティング = ファイル名

WordPressでは、ルーティング設定ファイルは存在しません
代わりに、テーマディレクトリ内のファイル名そのものがルーティングテーブルになります。

これを「テンプレート階層(Template Hierarchy)」と呼びます。

アクセス先 WordPressが探すファイル フレームワークで言うと
トップページ front-page.php Route::get('/')
投稿詳細 single.php Route::get('/posts/{id}')
固定ページ page.php Route::get('/about')
カテゴリ一覧 category.php Route::get('/categories/{slug}')
どれにも該当しない index.php Route::fallback()

つまり、ファイルを置くだけでルーティングが完成します。
設定ファイルに1行も書く必要がありません。

front-page.php(トップページ)

<?php get_header(); ?>

<main class="site-content">
    <h1>新着記事</h1>
    <?php if (have_posts()) : ?>
        <?php while (have_posts()) : the_post(); ?>
            <article class="post-card">
                <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
                <time><?php the_time('Y年n月j日'); ?></time>
                <?php the_excerpt(); ?>
            </article>
        <?php endwhile; ?>
    <?php else : ?>
        <p>記事がありません。</p>
    <?php endif; ?>
</main>

<?php get_footer(); ?>

have_posts()the_post() は、WordPressが自動的にデータベースから
記事を取得してループする仕組みです。
フレームワークでいえば、Controllerがデータを取得してViewに渡す処理に相当しますが、
WordPressではこれがテンプレートファイル内に一体化されています。

single.php(投稿詳細ページ)

<?php get_header(); ?>

<main class="site-content">
    <?php if (have_posts()) : ?>
        <?php while (have_posts()) : the_post(); ?>
            <article>
                <h1><?php the_title(); ?></h1>
                <time><?php the_time('Y年n月j日'); ?></time>
                <div class="post-body">
                    <?php the_content(); ?>
                </div>
            </article>
        <?php endwhile; ?>
    <?php endif; ?>
</main>

<?php get_footer(); ?>

投稿詳細へのアクセスは ?p=123 や パーマリンク設定に応じたURLで行われますが、
開発者が明示的にルーティングを書く必要はありません。
WordPressが single.php の存在を検知し、自動的にこのファイルを使用します。

index.php(フォールバック)

<?php get_header(); ?>

<main class="site-content">
    <h1>ページが見つかりません</h1>
    <p>お探しのページは存在しないか、移動された可能性があります。</p>
</main>

<?php get_footer(); ?>

テンプレート階層のどのファイルにも該当しない場合、
最終的に index.php が使われます。
フレームワークでいう フォールバックルート(404ハンドラー) に相当します。

index.php はWordPressテーマに唯一「必須」のファイルです。
これがないとテーマとして認識されません。

3. 共通レイアウト = テンプレートの継承

front-page.phpsingle.php に同じヘッダー・フッターを書くのは冗長です。
WordPressではこれを共通パーツとして分離できますが、
その仕組みの中に、プラグインが動作するための重要な伏線が隠れています。

フレームワークのレイアウト機能

フレームワークでは、ヘッダーやフッターなどの共通部分を
レイアウトファイルとして切り出し、各ページで継承します。

例:Laravelの場合(Blade)
@extends('layouts.app')
@section('content')
    ...
@endsection

例:Djangoの場合
{% extends "base.html" %}
{% block content %}
    ...
{% endblock %}

WordPressの場合 = get_header() / get_footer()

WordPressでは get_header()get_footer() で共通パーツを読み込みます。

header.php

<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
    <meta charset="<?php bloginfo('charset'); ?>">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
    <header class="site-header">
        <h1><a href="<?php echo home_url('/'); ?>"><?php bloginfo('name'); ?></a></h1>
    </header>

footer.php

    <footer class="site-footer">
        <p>&copy; <?php echo date('Y'); ?> <?php bloginfo('name'); ?></p>
    </footer>
    <?php wp_footer(); ?>
</body>
</html>

ここで重要なのは wp_head()wp_footer() です。
この2つの関数は、WordPressコアやプラグインが
CSSやJavaScriptを出力するためのフックポイントになっています。

これが次の章につながります。

4. フック = イベント駆動

WordPressの拡張性の「核」がここです。
コアファイルを1行も変更せずに、自分の処理を差し込める。
2004年のv1.2で導入されたこの仕組みが、
WordPressをただのブログツールから拡張可能なプラットフォームに変えました。

フレームワークのイベント/ミドルウェア

フレームワークでは、リクエスト処理の途中に独自の処理を差し込む仕組みがあります。

例:Laravelのミドルウェア
public function handle($request, Closure $next)
{
    // リクエスト処理の前に実行される
    Log::info('リクエスト受信');
    return $next($request);
}

例:Djangoのミドルウェア
class MyMiddleware:
    def process_request(self, request):
        # リクエスト処理の前に実行される
        logger.info('リクエスト受信')

WordPressのフック = add_action / add_filter

WordPressでは、コア処理の特定のタイミングに
add_action(アクションフック)で独自の関数を登録できます。

これを functions.php に記述します。

functions.php

<?php

/**
 * テーマのCSSを読み込む
 */
function mytheme_enqueue_styles()
{
    wp_enqueue_style(
        'mytheme-style',                          // ハンドル名(識別子)
        get_stylesheet_uri(),                     // style.cssのURL
        array(),                                  // 依存関係
        wp_get_theme()->get('Version')            // バージョン(キャッシュ対策)
    );
}
add_action('wp_enqueue_scripts', 'mytheme_enqueue_styles');

/**
 * ページタイトルをWordPressに自動管理させる
 */
function mytheme_setup()
{
    add_theme_support('title-tag');
    add_theme_support('post-thumbnails');
}
add_action('after_setup_theme', 'mytheme_setup');

/**
 * 投稿の抜粋(excerpt)の文字数を変更する
 */
function mytheme_excerpt_length($length)
{
    return 40;
}
add_filter('excerpt_length', 'mytheme_excerpt_length');

ここで注目すべきは、コアファイルを1行も変更していないことです。

wp_enqueue_scripts というイベントが発火するタイミングに、
自分の関数 mytheme_enqueue_styles を「登録」しているだけです。

フレームワークのイベントリスナーやミドルウェアと同じ構造です。

概念 フレームワーク WordPress
処理の前後に割り込む ミドルウェア アクションフック(add_action
出力を加工する レスポンスフィルター フィルターフック(add_filter
機能を初期化時に登録 サービスプロバイダ after_setup_theme フック

5. プラグイン = パッケージ管理

エンジニアにとってのcomposerやnpmが、
WordPressでは管理画面のGUIになっています。
やっていることは同じですが、「誰が使うか」によってインターフェースが異なります。

フレームワークのパッケージ管理

フレームワークでは、外部ライブラリをパッケージマネージャで管理します。

フレームワーク パッケージマネージャ インストール先
Laravel Composer /vendor/
Django pip site-packages/
Next.js npm / yarn /node_modules/

WordPressのプラグイン

WordPressでは、管理画面から「プラグイン」→「新規追加」で
機能を追加できます。

インストールされたプラグインは /wp-content/plugins/ に配置されます。

仕組みは同じです。違いはGUIで管理できるかどうかだけです。

WordPressのユーザーには非エンジニアも多いため、
コマンドラインではなくGUIという選択肢が用意されています。

フレームワーク:
$ composer require laravel/sanctum

WordPress:
管理画面 → プラグイン → 新規追加 → 「Contact Form 7」で検索 → インストール

なお、WordPress にも WP-CLI というコマンドラインツールがあり、
エンジニアはこちらを使うこともできます。

# WP-CLIでプラグインをインストール
$ wp plugin install contact-form-7 --activate

まとめ ― 「コアに触るな」は設計原則

ここまで見てきた内容を1枚の対応表にまとめます。

フレームワークの概念 WordPressでの実現方法
ルーティング テンプレート階層(ファイル名の命名規則)
レイアウト / テンプレート継承 get_header() / get_footer()
イベント / ミドルウェア フック(add_action / add_filter
サービスプロバイダ functions.php + after_setup_theme
パッケージ管理 プラグイン(/wp-content/plugins/
Convention over Configuration ファイル名による自動認識

WordPressの「コアファイルは触らず、テーマとプラグインで拡張する
というルールは、単なる慣習ではありません。

この構造を整理するのに便利な考え方として、
ソフトウェア設計の原則である
Open-Closed Principle(拡張に対して開き、修正に対して閉じる)
があります。SOLID原則の1つとして知られ、
フレームワークに限らずソフトウェア設計全般で広く使われている考え方です。

WordPressの開発者がこの原則を意識していたかはわかりません。
ただ、「コアを変更せず、テーマとプラグインで拡張する」という構造は、
まさにこの原則と同じ考え方で理解できます。

WordPressはCMSとして生まれましたが、
その内側には、フレームワークと共通する設計パターンが確かに存在します。
この視点を持つと、WordPressの「なぜそうなっているか」が腹落ちし、
今後の開発やトラブルシュートでも的確な判断ができるようになるはずです。

参考

株式会社BTMではエンジニアの採用をしております。
ご興味がある方はぜひコチラをご覧ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?