WordPress: 子テーマの仕組みについて調べてみた

WordPressは子テーマを使用すると親テーマのバージョンアップの影響を受けることなくカスタマイズできます。
どんな仕組みで子テーマが動作しているのか調べてみました。

子テーマ選択時の動作

テーマの管理画面でテーマを選択すると、データベースのoptionsテーブルに次のレコードが追加、または更新されます。

フィールド名
option_name option_value
'stylesheet' style.cssがあるディレクトリ
'template' テンプレートディレクトリ

option_valueフィールドには、選択したテーマのディレクトリ名がセットされます。
例えば /wp-content/themes/my-theme だとしたら、'my-theme' がセットされます。

現在のテーマが子テーマでないときは、'stylesheet''template'は同じ値になります。

しかし現在のテーマが子テーマのとき、つまりstyle.cssのヘッダに Template: が指定されているとき、Template: の値が'template'にセットされます。

子テーマのstyle.cssの例

WordPressの子テーマの解説より。

  • /*
  • Theme Name: Twenty Fifteen Child
  • Theme URI: http://example.com/twenty-fifteen-child/
  • Description: Twenty Fifteen Child Theme
  • Author: John Doe
  • Author URI: http://example.com
  • Template: twentyfifteen
  • Version: 1.0.0
  • License: GNU General Public License v2 or later
  • License URI: http://www.gnu.org/licenses/gpl-2.0.html
  • Tags: light, dark, two-columns, right-sidebar, responsive-layout, accessibility-ready
  • Text Domain: twenty-fifteen-child
  • */
AFFS Simple Code Viewer
Copy

実際には、「Theme Name:」「Template:」「Version:」の3つがあれば動作します。
(「Version:」はなくても動くかも?)

このとき、'twentyfifteen' が'template'にセットされるわけです。

親テーマと子テーマのディレクトリ特定

親テーマと子テーマのディレクトリパスは、定数TEMPLATEPATH および定数STYLESHEETPATH で取得できます。
このディレクトリは、フルパスです。

定数 内容
TEMPLATEPATH 親テーマのディレクトリ
STYLESHEETPATH 子テーマのディレクトリ

二つの定数が同じなら、現在のテーマが子テーマでないことが確認できます。

  • if ( TEMPLATEPATH === STYLESHEETPATH ){
  • // 現在のテーマが子テーマでないときの処理
  • }
AFFS Simple Code Viewer
Copy

各定数は次のように定義されています。

  • define( 'TEMPLATEPATH', get_template_directory() );
  • define( 'STYLESHEETPATH', get_stylesheet_directory() );
AFFS Simple Code Viewer
Copy

get_template_directory() はoptionsテーブルから 'template' の値を、get_stylesheet_directory() は 'stylesheet' の値を取得して、'ホームディレクトリ/wp-content/themes/' の後に結合したものを返しています。

なお、二つの定数はプラグインが実行された後にセットされます。
ほとんどないと思いますが、もしプラグインでテーマのパスを使用するなら、定数は使用できません。
上の関数を呼び出しましょう。

function.phpの実行順番

親テーマと子テーマの両方ともfunction.phpがある場合、子テーマ → 親テーマの順番で実行されます。

そのため同名の関数をfunction.phpに定義するとエラーになります。
親テーマの関数を子テーマで上書きするような使い方はできないので、注意が必要ですね。

フィルターやアクションは優先度が同じ場合、追加した順番で実行されます。
つまり同じ形式でadd_filter()またはadd_action()を実行すると、子テーマから実行されるのです。

翻訳データを親テーマと子テーマで個々に用意していて同じテキストドメインで翻訳データの読み込みをした場合は、二つの翻訳データは結合されます。
ただし、同じキーワードが存在する場合は、先に読み込んだ方が残ります。
アクションの優先度指定などをしない限り、タイミングとしては子テーマの読み込みが速いので、子テーマの翻訳が優先されますね。

翻訳データの読み込みについては、次の記事を読んでみてください。

WordPress: load_theme_textdomain()とload_plugin_textdomain()関数を少し詳しく解説してみる

テンプレートの選択

index.php や single.php などのテンプレートファイルは、次の順番でファイルが存在しているかチェックされます。

  1. 定数STYLESHEETPATH 内にあるか(子テーマディレクトリ)
  2. 定数TEMPLATEPATH内にあるか(親テーマディレクトリ)
  3. /wp-include/theme-compat/ 内にあるか

ファイルの存在が確認されると、そのファイルを読み込みます。
以降の存在チェックはおこないません。

現在のテーマが子テーマでない場合でも、①と②は実行されます。つまり同じファイルを2回チェックします。

記憶媒体へのアクセスは重い処理なので、少し無駄な気がしますね。
実際にはファイルの存在確認した結果をPHPがキャッシュしているので、速度的な問題はありません。
個人的にはキャッシュに頼るな、と言いたいですが。。。

③/wp-include/theme-compat/ には、次のファイルが格納されています。

comments.php
embed.php
embed-404.php
embed-content.php
footer.php
footer-embed.php
header.php
header-embed.php
sidebar.php

ヘッダーやフッターはデフォルトが用意されているようですね。
これ以外のテンプレートファイルは、例えばarchive.phpとかcategory.phpなどが存在しないとき、最終的にテーマディレクトリの index.php が選択されます。

ページタイプ毎のテンプレート選択については、次の記事を読んでみてください。

WordPress: 種類毎に選択されるテンプレート名の一覧を調べてみた

function.phpやテンプレート以外の子テーマ適用

function.phpやテンプレート以外は、子テーマで同名ファイルを用意しても切り替えてくれません。

あまり需要がないんでしょうね・・・
無理やり場面を想定してみます。

ヘッダー画像を切り替えたい

Webページにヘッダー画像が表示されているとします。
このヘッダー画像は、テーマディレクトリの直下にあります。
子テーマに同名の画像ファイルがあるときは、ヘッダー画像をそちらに切り替えます。

/wp-content/themes/
   ┣ my-theme
   ┃     ┗ header-logo.png
   ┃ 
   ┗ my-theme-child ← my-themeの子テーマ
         ┗ header-logo.png ← この画像を使いたい!

こんな場面は、ありそうですね。

親テーマのfunction.phpに次の関数を記述します。
ファイルが存在するかどうかを、確認する関数です。

  • function select_theme_file($path,$no_file=false){
  • if ( file_exists( STYLESHEETPATH . '/' . $path ) ) {
  • return STYLESHEETPATH . '/' . $path;
  • }
  • if( STYLESHEETPATH === TEMPLATEPATH ) return $no_file;
  • return file_exists( TEMPLATEPATH . '/' . $path ) ?
  • TEMPLATEPATH . '/' . $path : $no_file;
  • }
AFFS Simple Code Viewer
Copy

親テーマのheader.phpで、ロゴ画像のimgタグで関数を呼び出します。

  • <img src="<?php select_theme_file('header-logo.png'); ?>">
AFFS Simple Code Viewer
Copy

テストしてないけど、これでいけるはず。

実はWordPressは同じようなファイルチェックをlocate_template()という関数でおこなっています。
関数を自作しなくても、この関数を使うことができます。

  • <img src="<?php locate_template('header-logo.png'); ?>">
AFFS Simple Code Viewer
Copy

この関数はテンプレート用に設計されているため、/wp-include/theme-compat/のチェックを行っていたり、テンプレートファイルの読み込みも可能になっています。
テンプレート以外のファイルをチェックするだけなら少し過剰ですが、自作するよりお手軽かもしれません。