WordPress 徹底解析(WordPressを拡張するプログラムの構成法 – テンプレートファイルからの情報出力)

テンプレートファイルに情報表示用の記述を追加し、カスタムフィールドに格納した値を、クラス化した処理から出力する手法を紹介します。 🙂


テンプレートファイル用のヘルパー関数

前回までで、記事公開時にカスタムフィールドへのお天気情報の登録と、そのテンプレートファイルへの出力を行うヘルパー関数の実装までができました。

今回は実際にカスタムフィールドの値をHTML出力する処理を記述してみます。

出力部分のみ抜き出した概念ソースは以下のようになります。

class Otenki {
    function output() {
        // お天気情報出力処理
    }
}

$otenki = new Otenki();

function the_otenki() {
    global $otenki;

    $otenki->output();
}

本処理を行った場合の、記事投稿時の処理シーケンスは以下のようになります。

otenki_seq4

テーマ・テンプレートファイルのお天気情報を出力したい部分で、the_otenki 関数の呼び出しを記述することで出力を行います。

お天気情報は記事ごとの情報になりますので、WordPress ループ内に実装します。

<?php while(have_posts()) : the_post(); ?>
    <?php the_title(); ?>
    <?php the_otenki(); ?>
    <?php the_content(); ?>
<?php endwhile; ?>

ヘルパー関数 the_otenki を介して実際に HTML 出力処理を行う output メソッドでは、カスタムフィールドを特定するために、ループ中の記事IDが必要になります。

このような場合、通常のテンプレートファイルと同様、拡張の処理中でも記事IDを取得する get_the_ID() テンプレート関数を使うことが可能です。 これはシーケンス図から見て取れるように、処理が WordPress ループ内で呼び出されるためです。

カスタムフィールド取得や正当性確認などの処理を入れると、シーケンスは次のようになるでしょう。

otenki_seq5

実際の output メソッドの実装は以下のようになります。

function output() {
    // ループ中の記事IDを取得
    $post_ID = get_the_ID();
    // カスタムフォールドから天気情報取得
    $otenki = get_post_meta($post_ID, Otenki::META_KEY, true);
    // カスタムフィールドが正しい形式でなければ何もしない
    if(!(isset($otenki[Otenki::LWWS_V1_JSON])
        && $this->isOtenki($otenki[Otenki::LWWS_V1_JSON]))) return;
    // お天気情報を元に HTML を構築して出力
    $today = $otenki[Otenki::LWWS_V1_JSON]['forecasts'][0];
    $image = $today['image'];
    $telop = $today['telop'];
    echo "<img src=\"${image['url']}\""
        . " width=\"${image['width']}\" height=\"${image['height']}\""
        . " alt =\"${telop}\""
        . " class=\"$this->css_class\" />\n";
}

get_the_ID 関数でループ中の記事IDを取得し、get_post_meta 関数でそのカスタムフィールド値を取得し正当性確認後、HTML に出力するのが大きな流れです。

出力処理のアクションフック化

今回のように WordPress のテンプレートファイルに対して出力用のテンプレートタグを増やす拡張では、テンプレートファイルと拡張がかかれたモジュールの関係を維持する必要があります。

具体的に言えば、この拡張をプラグインとして定義した場合に、the_oteki テンプレートタグがかかれたテーマとプラグインはセットで扱う必要があります。つまり、プラグインを無効化すると the_otenki 関数が定義されなくなるため、PHP がエラーとなりサイトがダウンしてしまいます。

(functions.php に実装された場合は、必ずテーマとセットで有効になりますのでこの問題は発生しません。)

この問題に対処するために拡張をプラグイン化した場合には、その存在をテンプレートファイルで確認する必要があります。

グローバル変数 $otenki の存在チェック。

<?php if(isset($otenki)) the_otenki() ?>

グローバル関数 the_otenki の存在チェック。

<?php
if(function_exists('the_otenki')) {
    the_otenki();
}
?>

以上のようにいくつか方法がありますが、いずれもテンプレートファイルの記述は冗長になります。存在確認の処理はテンプレートファイルでは意識したくないところです。

そこで、WordPress のアクションフックの仕組みを利用して確認処理を削除するテクニックを用います。

テンプレートファイルに記述する関数呼び出しを次のようにアクションフックの呼び出しに変更します。

<?php while(have_posts()) : the_post(); ?>
    <?php the_title(); ?>
    <?php do_action('the_otenki'); /* ←ここ */ ?>
    <?php the_content(); ?>
<?php endwhile; ?>

そして処理を行う output メソッドを、新設した the_otenki アクションに登録します。

$otenki = new Otenki();

add_action(
    'publish_post', array($otenki, 'prepare'), 10, 2);
add_action(
    'extract_otenki', array ($otenki, 'extract'), 10);
add_action(
    'the_otenki', array($otenki, 'output')); /* ←ここ */

アクションフックを実行する do_action 関数は、登録された関数がないと何も処理を行いません。

つまり output メソッドがない(=プラグインが有効化されていない)場合にもエラーを起こさずに素通してくれるようになるため、冗長な存在確認の実装を削除することができます。

このようにするとプラグイン無効時の考慮だけでなく、合わせて the_otenki が呼び出されたタイミングを他のからもフックできるようになります。

ここからさらにアクションフックを使ったプログラミング手法を用いて、たとえば、お天気を出したくないときの条件制御を、他の拡張から動的に行うなどという実装も行えることでしょう。

まとめ

地域ID指定の管理画面化を除き全実装が完了しました。

今回のポイントは、

  • 拡張内でも WordPress のテンプレートタグ・関数が用いることが出来る。その場合は WordPress の動作シーケンスを意識しよう。
  • 拡張を functions.php ではなくプラグインとして実装し、テーマと関連する関数がある場合は呼び出し元で存在チェックをしよう。
  • 存在チェックが冗長になる場合はアクションフックの仕組みを用い、新設したアクションを用いて処理関数を登録すると処理削除とともに拡張性も向上する。

でした。

WordPress のフック処理は強力です。なんとバージョン 1.2 から備わる由緒正しい機能で、WordPress のプログラム構成上の最大の特徴といっても過言ではありません。

フックを使いこなすことにより、横断的関心事に対して様々な処理を柔軟に拡張していくことができます。自身で拡張を実装する場合も、フックを積極的に使うことで WordPress 的なプログラミングができるようになるでしょう。

というわけで、次回は地域IDを指定する管理画面を付与してみます。 お楽しみに。 🙂

ソース

最後にここまでの、全てのソースコードを記載します。

コンストラクタで地域IDを指定する形ですが、プラグインとしては全機能が動く状態です。プラグインファイルもしくは functions.php にコピーすることで動作します。

今までの解説が全て実装されていますので、プログラムの流れを考えながら是非読んでみてください。

<?php
/******************************************************************************
 * Otenki Class
 * 
 * @author     hiromasa
 * @version    1.00
 *****************************************************************************/
class Otenki {

    /**
     * カスタムフィールド・メタキー.
     */
    const META_KEY = 'otenki';

    /**
     * APIバージョン情報(V1/JSON).
     */
    const LWWS_V1_JSON = 'LWWS_V1_JSON';

    /**
     * LWWS URL 引数(地域別値).
     * 
     * @see http://weather.livedoor.com/forecast/rss/primary_area.xml
     */
    var $city;

    /**
     * 画像出力時の CSS クラス.
     */
    var $css_class;

    /**
     * コンストラクタ・プラグインデフォルト値設定.
     */
    function __construct($city = '016010', $css_class = 'otenki') {
        // LWWS URL 引数(地域別値)
        $this->city = $city;
        // 画像出力時の CSS クラス
        $this->css_class = $css_class;
    }

    /**
     * 記事公開フックで天気情報取得スケジュールを作成する.
     * 
     * @param $post_ID
     * @param $post
     * @return none
     */
    function prepare($post_ID, $post) {
        // "今日"のポストの時だけ天気を取得
        if(date("Y/m/d", strtotime($post->post_date))
            != date("Y/m/d")) return;
        // 天気取得の非同期スケジュール作成
        wp_schedule_single_event(
            time()
            , 'extract_otenki'
            , array($post_ID));
    }

    /**
     * LWWS から天気情報を取得し記事のカスタムフィールドに格納する.
     * 
     * @param $post_ID
     * @return none
     */
    function extract($post_ID) {
        // LWWS(V1/JSON)より天気情報を取得
        $json = wp_remote_get(
            'http://weather.livedoor.com/forecast/webservice/json/v1?'
            . http_build_query(array('city' => $this->city)));
        // 取得できなかった場合は次回の公開に期待
        if(is_wp_error($json)) return;
        // リクエストから JSON データ取得し PHP 連想配列に変換
        $otenki = json_decode($json['body'], true);
        // 正常な JSON 形式ではなければ次回の公開に期待
        if(!$this->isOtenki($otenki)) return;
        // カスタムフィールドにバージョン情報付きで格納
        update_post_meta(
            $post_ID
            , Otenki::META_KEY
            , array(Otenki::LWWS_V1_JSON => $otenki));
    }

    /**
     * カスタムフィールドに取得したお天気アイコン用 HTML をアクションフックで出力.
     * 
     * @param none
     * @return none
     */
    function output() {
        // ループ中の記事IDを取得
        $post_ID = get_the_ID();
        // カスタムフォールドから天気情報取得
        $otenki = get_post_meta($post_ID, Otenki::META_KEY, true);
        // カスタムフィールドが正しい形式でなければ何もしない
        if(!(isset($otenki[Otenki::LWWS_V1_JSON])
            && $this->isOtenki($otenki[Otenki::LWWS_V1_JSON]))) return;
        // お天気情報を元に HTML を構築して出力
        $today = $otenki[Otenki::LWWS_V1_JSON]['forecasts'][0];
        $image = $today['image'];
        $telop = $today['telop'];
        echo "<img src=\"${image['url']}\""
            . " width=\"${image['width']}\" height=\"${image['height']}\""
            . " alt =\"${telop}\""
            . " class=\"$this->css_class\" />\n";
    }

    /**
     * お天気情報バリデーション.
     * 
     * @param unknown $otenki
     * @return boolean
     */
    function isOtenki($otenki) {
        if(!(is_array($otenki)
            && isset($otenki['forecasts'])
            && is_array($otenki['forecasts'])
            && isset($otenki['forecasts'][0]['telop'])
            && isset($otenki['forecasts'][0]['image']))) {
            return false;
        }
        return true;
    }
}

/******************************************************************************
 * Otenki - WordPress Interface Define
 *****************************************************************************/

$otenki = new Otenki();

add_action('publish_post', array($otenki, 'prepare'), 10, 2);
add_action('extract_otenki', array ($otenki, 'extract'), 10);
add_action('the_otenki', array($otenki, 'output'));
?>

目次

本連載はファイルがない状態からソースコードを実装していく方式を採っています。プログラムの構成法も含めて解説していますので、最初から読んでいくとより理解しやすいでしょう。

アクションフックのプラグインをつくる編
最初に、WordPress の拡張において重要なフックの考え方を、サンプルの動作のひとつである「記事公開時の処理追加」の実装を元に解説します。フックはこの後の画面出力、処理の非同期化や管理画面の付与でも使われていきます。
処理のクラス化
WordPress 拡張におけるモジュール設計として、PHP のクラスを用いた方法を紹介します。分かりやすく、他のモジュールとの競合を最小限に抑えられる手法です。
外部 API の呼び出し
LWWS などの REST API に対して WordPress からリクエストを送る解説です。また、ユーザによって設定が異なる値がある場合のモジュール構成も合わせて紹介します。
外部 API 呼び出しの非同期化
外的要因などで処理スピードの低下が懸念される場合の処理方法です。WP-Cron を用いたスケジュール化手法を解説しています。
テンプレートファイルからの情報出力(この記事)
テンプレートファイルに処理を記述し出力する場合に、拡張モジュールと疎結合にする実装法を紹介します。アクションフックの活用例としても有効です。
管理画面をつける
管理画面に設定欄を設けユーザが拡張の機能をコントロールできるようにする手法の解説です。また、本連載中のソースコードに最終的なリファクタリングをかけ、他の開発者にこの拡張の意味を表現するプログラミングテクニックも解説します。
まとめ
本連載の概要とまとめ、そして目次です。

コメントを残す