get_template_part()に引数として任意の変数を渡せるようになった

最終更新:2020-12-06 by Joe

すこしWordpressのテーマ開発に慣れてきた方は、get_template_part()で展開したテンプレートファイル内で、外の(スコープ外の)変数を参照できないという課題感を経験すると思います。

get_template_part()とは?

まずは、get_template_part()の期待された使い方から。

WordPress: devサイトのマニュアルから:

// テンプレート部品をテンプレート内にロードする
get_template_part( string $slug, string $name = null )

引数の最初と最後でファイル名を指定します。すなわち

// テーマフォルダ内の「content-product.php」をロードする
<?php get_template_part( 'content', 'product' ); ?>

のように使います。

もちろん、単純にinclude してもWordpressテーマは動作するのですが、このget_template_partには、テーマの親子関係をハンドルしたり、Wordpressフック関数を実行したり、いくつかのワードプレスの設計思想に基づく仕組みが含まれています。

WordPressは歴史的な事情もあり、必ずしもかっちりとした設計思想とは呼べないのですが、やはり設計者の意図から不用意に逸脱すると、構造が複雑になりがちですので、テンプレートファイルを取り込みたい時は、極力この関数を使うべきでしょう。

2020/12/31 version 5.5 から引数が渡せるようになった!

この記事は「get_template_part() では、引数は渡せない」という趣旨で書かれた記事でしたが、2020年12月現在、最新バージョンは、引数をが利用可能です。

8年ごしにユーザの要望が、ついにこたえられたようです。

Passing arguments to template files in WordPress 5.5

関連するチケット:


以下は、これ上記の拡張以前の内容です。2020年12 月現在、無効です。

get_template_part()について想うこと

上記の例では、「content-product.php」ファイル内部で、その呼び出し元に定義されている変数を参照できません。get_template_part内(関数内)ですので、PHPの言語仕様上、関数の外のスコープを参照することはできないからです。(どうしても参照したいときは「global $variable;」を記述しますが、globalを思いつきベースで使うのは良い方法ではありません・・。)

これもWordpressの設計思想に基づく部分に起因していると思われますが、でも、やはり「関数の外側の変数を参照したくなってしまう」という状態は、多くのテーマ開発者に生じているのが事実です。

get_template_part()に引数で、変数を渡して参照させる事ができせれば・・

実際に、このことは過去に何度もWordpress開発者の間で議論されていました。ただし、今の所は単純な結論には、至っていないようです。

参考:Trac
Ticket #21676 “Pass a variable to get_template_part()”

英語で長いスレなので読みにくいですが、Wordpress Coreチームの Dev Lead, Nianの返信から、かいつまむと、

get_template_part() is designed for template modularization and child theme inheritance, let’s not complicate it further.

意訳:
get_template_partは、テンプレートのモジュール化と、チャイルドテーマへの継承を意図してデザインされている。これ以上複雑にする(≒関数に多くの役割を与える)のはやめておこう。

また、別の返信で、

I would hesitate to add anything to this API without clearing setting a new standard for theme development, much the way get_template_part() did. For example, if we did this, I would want a clean break from all global scope.

意訳:
テーマ開発における新しい規約を設けずに、このAPI(≒get_template_part)に何かを追加するのは避けたい。もしやるのであれば、むしろすべてのグローバルスコープ(を利用した設計)をやめてしまいたい。

要は「ワードプレスの設計思想上、(引数追加による変数渡しを)実装する事が必ずしも好ましくない」というのが答えのようで、「そんな機能があったら便利だろう」というのは認めつつも、直近では実装される意図はないようです。

このTracスレッドはもう4年ほど前のものですが、現状の最新Wordpressとしても、この目的のための拡張はリリースされていません。

つまるところ、「テンプレートファイルに、get_template_part()をつかって、関数内に引数として、変数を渡すことはできません」。

参考:
https://wpdocs.osdn.jp/関数リファレンス/get_template_part

get_template_part()に引数を渡すための代替策

1)locate_templateでincludeする

さて、get_template_part()関数に「引数を渡す」という事はできないのは関数の仕様なので「しょうがない」のですが、代替方法として、多くのワードプレスのテーマ開発者が迂回策として使っている方法があります。

こちらです。

include locate_template( 'xxxx.php' );

これにより、実際に起きることは「includeする」というだけなので、

include get_template_directory() . '/xxxx.php';

としてもよいのですが、前者のlocate_template()関数を利用したほうが、すっきり書けると思います。

また、locate_templateには、テーマの親子関係があるとき・ないとき、参照するディレクトリのフォールバック処理も内包されています。(これは細かい話なので、厳密にはコード(Trac)をごらんください。)

include locate_template()作戦を利用する場面の例:

おそらく、あたなの目的を達成するために、上記で紹介した「include作戦」を選択する前に、それとは別の実現方法があるような気がします。

ただ、よく吟味した結果として、locate_templateを選択するかもしれません。

例えば、単純に「1テンプレート内のコード行数が肥大化したので分けたい」「再利用したいUI部品をモジュール化したい」といったケースはよく発生すると思います。分離したモジュールで外側の変数を参照したいことはいかにも発生しそうです。

このようなケースでは、もちろんincludeを使っても問題なさそうですが、「変数を関数経由で取得するようにする」などでも迂回できそうです。(functions.php内に、必要なヘルパー関数を追加する方法は一般的です。)

特に投稿のループであれば、get_the_ID()、get_query_var()などの投稿データやクエリ、クエリ結果へのアクセサはほぼ全て用意されていると思います。

その他のケースとしては、私は以下のような例で使用したことがあります。

下記のページテンプレートが必要だとします。サイトの運営者がWP管理画面から任意でテンプレートを選べるようにしたいのです。

いつくかの固定ページ用テンプレートを用意しましたが、実際は9割page.phpと同じコードです。

page.php
page-templates/rich-page-a.php // page.phpと9割同じ
page-templates/rich-page-b.php // page.phpと9割同じ
page-templates/rich-page-c.php // page.phpと9割同じ

page.phpのコードををrich-page.phpとして複製したくありません(コピペ地獄の始まりです)メンテ工数を著しく悪化させる事が目に見えています。

その代わりに、rich-page-a.php内で、locate_template( ‘page.php’、true );として、page.phpを再利用してみます。

/**
 * Template Name: Rich Page (A)
 */

// 参照したい値など、このテンプレート特有の変数・処理
$tagline = 'la la la love song';
$caption = get_post_meta($post->ID, 'page_title_caption', true);

// Includeする
include locate_template('page.php');

これを行う事により、管理画面からテンプレートを設定するときに、「Rich Page (X)」を適応することで、テンプレートの実態はpage.phpを使いながら、いくつかの参照可能な変数や、テンプレート特有の処理を、そのページテンプレートファイルごとに、切り出して記述することができます。

もちろん、一つのpage.php内に分岐を書き込むんでも実現できます(下記の例)。ただし、テンプレートファイル名をコード内に書き込む必要がありすし、類似テンプレートの数が増えるほど、分岐のif else 処理が長く煩雑になっていく事は容易に想像できると思います。

// すべての処理をpage.phpに書き込むと、乱雑になる事がある


// 設定されたページテンプレートのスラッグを取得
$slug = get_page_template_slug( get_the_ID() );

if ( $slug === 'rich-page-a' ) {

  // 処理・・・

} else if ( $slug === 'rich-page-b' ) {

  // 処理・・・

} else if ( $slug === 'rich-page-c' ) {

  // 処理・・・

}

参考:
https://codex.wordpress.org/Function_Reference/get_page_template_slug

2)それ以外の方法?global?

他の方法として、テンプレート内で「global $your_variable」とすることで、関数外のスコープへのアクセスが可能になりますが、やはりあまりお勧めできません。外のスコープの値を参照したいと思う度に、globalだらけになっていきます。あなたの使っているプラグインと変数名がコンフリクトするリスクは?

いずれにしても、いまのところ、私は上記のinclude方法で、やりくりできています。


get_template_partまとめ

以下、まとめです。

get_template_part()に引数は渡せないが・・・

改めて、上記のinclude作戦は、「引数を渡している」わけではないです。「現在のページテンプレートの運用上の工夫」くらいのものでしょうか。繰り返しですが、もちろん別の方法もあるかと思います。このようなケースで上記が最良だという事ではないです。

補足1: locate_template()関数について

ちなみに、このlocate_template()関数は、見慣れない人もいるかもしれませんが、得体のしれない関数では全くありません。まさに、get_template_part()の中で使われている、別ファイルをロードするための関数そのものです。

実際の、get_template_part()自体のソースコードも極めてシンプルです。

ソースコード:wp-content/general-template.php

function get_template_part( $slug, $name = null ) {
  /**
   * Fires before the specified template part file is loaded.
   *
   * The dynamic portion of the hook name, `$slug`, refers to the slug  name
   * for the generic template part.
   *
   * @since 3.0.0
   *
   * @param string $slug The slug name for the generic template.
   * @param string $name The name of the specialized template.
   */
  do_action( "get_template_part_{$slug}", $slug, $name );

  $templates = array();
  $name = (string) $name;
  
  if ( '' !== $name )
    $templates[] = "{$slug}-{$name}.php";

  $templates[] = "{$slug}.php";

  locate_template($templates, true, false);
}

補足2:あなたのglobalは本当に必要か?

WordPressのテーマ開発において、特に投稿オブジェクトへのアクセスに関しては、多くの場合「global $post」とする代わりに、適切なアクセサ関数が用意されているはずです。もし、メインクエリ結果へのアクセスであれば、get_query_var( ), get_queried_object(), get_post_field()関数などなど、解決できたりします。

補足3:議論を呼ぶget_template_part()

StackExchangeでも、似たような議論がいくつかみられます。

Passing variables through locate template in StackExchange
http://wordpress.stackexchange.com/questions/4462/passing-variables-through-locate-template

以上になります。

あまりパリッとしない内容でしたが、正直わたしもあまりこの問題にはパリッとしていないです。Rails(blade)とかのテンプレート設計とは、すこし世界観が違うということですね。

参考リンク

locate_template() in developer.wordpress.org:
https://codex.wordpress.org/Function_Reference/locate_template

get_template_part() in Trac:
https://core.trac.wordpress.org/browser/tags/4.6/src/wp-includes/general-template.php

locate_template() in Trac:
https://core.trac.wordpress.org/browser/tags/4.6.1/src/wp-includes/template.php