WordPress 徹底解析(WordPressを拡張するプログラムの構成法 – 外部 API の呼び出し)

アクションフックを使った例を用いて、WordPress プログラミングを紐解くシリーズ。引き続きまして「WordPress 徹底解析(WordPressを拡張するプログラムの構成法 – 外部 API の呼び出し)」です。

今回は LWWS お天気APIを呼び出して、記事公開時に当日のお天気情報をカスタムフィールドに入れるところまで進めてみます。


コンストラクタを使って地域IDを指定する

前回まででできあがった骨組みソースは以下のようなものでした。

プログラムがクラス化され、記事の公開を表すアクションフック「publish_post」に Otenki クラスのメソッド「prepare」を定義し、記事公開時に処理が呼び出されるところまでできています。また、テーマ用のお天気出力テンプレートタグ the_otenki も用意しました。

class Otenki {

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

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

    /**
     * 記事公開フックで天気情報を取得をする.
     *
     * @param $post_ID
     * @param $post
     * @return none
     */
    function prepare($post_ID, $post) {
        // お天気 API を呼び出し
        // Otenki::META_KEY, Otenki::LWWS_V1_JSON を用いて
        // カスタムフィールドに登録
    }

   /**
    * カスタムフィールドに取得したお天気アイコンを出力.
    *
    * @param none
    * @return none
    */
    function output() {
        // Otenki::META_KEY, Otenki::LWWS_V1_JSON を用いて
        // カスタムフィールドを取得しアイコン HTML を出力
    }
}

$otenki = new Otenki();
add_action(
    'publish_post', array($otenki, 'prepare'), 10, 2);

/**
 * お天気情報を出力するテンプレートタグ.
 */
function the_otenki() {
    global $otenki;
 
    $otenki->output();
}

まず、今回利用する外部API(LWWS)の仕様です。

LWWS のお天気APIは、指定された地域IDで RESTのリクエストをなげると、JSON 形式で当日以降のお天気情報が返却されるという動作が行われます。

(例)「福岡県・久留米の天気」を取得する場合
下記URLにアクセスしてJSONデータを取得します。
基本URL + 久留米のID(400040)
http://weather.livedoor.com/forecast/webservice/json/v1?city=400040

ここで指定するお天気を取得する地域ID は使う人によって変わりますので、何かしらの方法で指定する実装が必要です。

いくつかやり方が考えられますが、ここではオブジェクトのインスタンス生成時に地域IDを指定し、コンストラクタからメンバ変数に値を格納することにします。合わせて、後続でつかいそうなお天気情報出力時の CSS クラス名も設定できるようにしました。

class Otenki {
    /* 略 */

    /**
     * LWWS URL 引数(地域別値).
     *
     * @see 
     */
    var $city;

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

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

    /* 略 */
}

$otenki = new Otenki();
add_action(
    'publish_post', array($otenki, 'prepare'), 10, 2);

function __cunstruct はインスタンス生成時に PHP によって呼び出される、コンストラクタと呼ばれる特殊なメソッドです。

次のようにクラスが new されたとき、__cunstruct メソッドは自動的にコールされ処理をそこに移します。

$otenki = new Otenki();

コンストラクタの引数には、オブジェクトの new をするときに指定した引数値が渡されます。

上記の new Otenki() の場合は引数の指定がないため、コンストラクタ(__construct)のデフォルト引き数値である、’016010′ と ‘otenki’ がメンバ変数 $city(地域ID) と $css_class (CSSクラス名)に格納され保持されます。

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

たとえば、久留米を地域IDに指定したい場合は、LWWS オブジェクトの生成を次のように書き換えます。

$otenki = new Otenki('400040');

さて、ここから少し余談です。オブジェクト指向、いかがですか。

このようなクラスとインスタンスの性質を利用すると、複数の地域の処理も同一のクラス定義で実行できます。

$sapporo = new Otenki();
$kurume = new Otenki('400040');
add_action(
    'publish_post', array($sapporo, 'prepare'), 10, 2);
add_action(
    'publish_post', array($kurume, 'prepare'), 10, 2);

/**
 * お天気情報を出力するテンプレートタグ.
 */
function the_otenki() {
    global $sapporo;
    global $kurume;
 
    $sapporo->output();
    $kurume->output();
}

処理シーケンスのイメージ的には次のようになります。 初期化 otenki.php からの <<new>> 部分で 2つのインスタンスが生成されていること注目してください。

otenki_seq2

単純な上位部への処理追加でいくつでも対象地域を増やせるようになっています。 🙂

ただし実装上、これからまだ考えるべきことが残っていることもシーケンスから見てとれるかもしれません。

DB へのデータ保存時のキー設計(カスタムフィールドを地域別にする?)や、ソースを修正することなく設定で動的に地域の増減を行いたい場合は、生成したインスタンスを管理する実装などが該当します。

これらの処置はそれほど難しくないものの、今回のサンプルソースとしては若干意味がぼやけてしまいそうなので、つくり込みは省略し他で解説することにしましょう。

オブジェクト指向の基本的な使い方のひとつを紹介したく、若干脱線してみました。普段オブジェクト指向が分からないと感じている方は、まずはシーケンスの確認を。異なる値を持った対象が new によってつくりだされ、それぞれを区別して処理を呼べる感覚が分かれば出来たも同然です。

ではここからは単純に、コンストラクタで地域IDが指定できるようになったことに注目していきます。 コンストラクタを用いることで処理に必要な変数の初期化のタイミングを得ることができます。

以上で地域IDの可変化を行う準備が整いました。まだ、ソースコードファイルを修正しないとユーザが地域IDが設定できませんが、これは後の管理画面までのお楽しみとしておきましょう。 🙂

外部 API からのお天気情報取得

外部 API を呼び出しお天気情報を取得します。

コンストラクタで設定した、地域ID($city) とアクションフックから渡された投稿記事情報 $post_ID, $post を元に、記事の公開時に WordPress によって呼び出される prepare メソッドを実装します。

/**
 * 記事公開フックで天気情報を取得をする.
 *
 * @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;
    // 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));
}

ソースを上から順に追っていきます。

まず最初に、公開された記事が当日かを判定します。これはお天気APIが”本日”の情報を返却するためです。過去日の記事の公開に対して取得を行うと”本日”の情報で上書きされてしまうため、これを抑止します。

// "今日"のポストの時だけ天気を取得
if(date("Y/m/d", strtotime($post->post_date))
    != date("Y/m/d")) return;

公開対象記事の情報を知るにはアクションフックから渡されてきた $post の情報を用いています。(var_dump($post) すると分かりやすいでしょう)

次にいよいよお天気 API に対して REST リクエストをなげます。 WordPress にはこのような http での外部情報取得のために wp_remote_get 関数が用意されてます。

実はこの処理に、古いソースでは PHP 標準関数の file_get_content を忘れて使ってしまっていたのでした。(まがりんさんご指摘ありがとうございます)

WordPress に準備された wp_remote_get 関数は、条件により適切な PHP の通信関数を選択がされ、詳細な通信ステータスの取得ができる機能を持っています。また、内部処理にフックがあちこちに仕掛けられており外部からの制御も可能ですので、WordPress で外部通信を行う場合はこちらを用いましょう。

使い方は簡単で、引数に API などのエンドポイント URL を渡し、is_wp_error で通信エラーハンドリングします。

URL に、コンストラクタで設定したメンバ変数($this->city)から地域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;

wp_remote_get で取得したデータ本体は、$json[‘body’] に格納されます。

取得した JSON 形式を PHP の連想配列に変換し、正しいデータ形式かの正当性確認(バリデーション)を行います。

// リクエストから JSON データ取得し PHP 連想配列に変換
$otenki = json_decode($json['body'], true);
// 正常な JSON 形式ではなければ次回の公開に期待
if(!$this->isOtenki($otenki)) return;

外部 API から取得したデータのバリデーションチェックは非常に重要です。期待値が返ってくればよいものの、異常データが戻ってきた場合、特に連想配列で処理している局面で PHP が警告を発砲し不具合になる場合があります。

また、お天気の HTML の出力ではここで登録されたカスタムフィールド値の取得処理を行いますが、こちらも同様に、ユーザが手で不正な値を登録されたり変更されると不具合になる可能性がありますので、念のためバリデーションしておいたほうがよいでしょう。

このように2つのメソッドで使われる同じバリデーションになりますので、ここでは isOtenki メソッドとして外だしにしています。

連想配列を、LWWS の仕様を元に”本日のお天気”部までツリーをたどり、存在するかを確認しています。

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

PHP の配列仕様に、string 値に添え字をつけるとその位置の1文字が返ってくるというちょっと悲しい下位互換機能があるため、少ししつこい判定になっています。

バリデーションを抜け正しいお天気情報だと確認できたら、いよいよカスタムフィールドに登録です。

アクションフックからもらえる記事ID($post_ID)と、カスタムフィールド用のキーをもって、カスタムフィールドに連想配列を全て登録します。API のバージョンアップを鑑みて API バージョン情報も独自に付与しています。

// カスタムフィールドにバージョン情報付きで格納
update_post_meta(
    $post_ID
    , Otenki::META_KEY
    , array(Otenki::LWWS_V1_JSON => $otenki));

格納する情報は、お天気出力処理に必要な”今日のお天気”だけでよいのですが、予報の文言なども面白いためそのまま全部入れています。(この辺は趣味です)

というわけで、ようやくAPI の呼び出しと、カスタムフィールドへの登録まで完成しました。 🙂

今回のポイントは、

  • クラス化した処理の、メンバ変数の初期化や設定はコンストラクタで行える。
  • 複数のインスタンスを使い処理を簡略化するオブジェクト指向の技がある。
  • 外部 API への http リクエストには wp_remote_get を使う。
  • 外部 API からの返却値はバリデーションをしっかり行う。

でした。

WordPress と PHP プログラミングテクニックの組み合わせの実例ということで、それぞれ単独でみるよりも面白いんじゃないかと思って書いてみましたが、いかがでしょうか。。

では、また次回。 🙂


目次

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

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

WordPress 徹底解析(WordPressを拡張するプログラムの構成法 – 処理のクラス化)

「WordPress 徹底解析(WordPressを拡張するプログラムの構成法 – 処理のクラス化)」いきます。 🙂

前回は、アクションフックの動きと簡単な処理の実装まで行ってみました。

実はこの記事、”プラグインをつくる”としてしまったのですが、WordPress のフックの機能はテーマの functions.php でも同様に使える手法です。

テンプレートファイルの functions.php は、プラグインファイルと読み込み順番が若干違うだけでプラグインと同様な実装ができます。テーマと同時に有効になるプラグインファイルと考えても差し支えないでしょう。というわけで、テーマを主につくる方も読み進めていただければと思います。紹介しているソースも functions.php にかけばそのまま動くはずです。

さて、今回は以下のような WordPress を拡張する上でのプログラム構成のノウハウを、引き続きアクションフックのプラグインのソースを元に紹介していきます。

  1. 処理のクラス化
  2. 外部 API の呼び出し
  3. テンプレートファイルに情報を出力する方法
  4. 外部 API 呼び出しの並列化
  5. 管理画面をつくる

まずは「1. 処理のクラス化」より。


処理のクラス化

前回紹介した、アクションフックを使う基本コードは以下のようなものでした。

function prepare($post_ID, $post) {
    die("test");
}

add_action('publish_post', 'prepare', 10, 2);

これをベースに以下の処理を実装していきます。

  • 「記事の公開時」にその日のお天気を LWWS API に問い合わせカスタムフィールドに格納する。
  • 格納されたお天気情報をテンプレートファイルの記述でアイコン出力する。

大きく2つの機能があり、双方ともカスタムフィールドの値の処理(設定と取得)で関連しています。これらの処理には関数が2個以上は必要そうです。

プラグインやテンプレートファイルの functions.php に定義する関数や変数はグローバルスコープになります。WordPress 本体や他のプラグインと共通的な空間に定義されますので、複数が大きくなり関数が増えるとお互いの干渉が気になってきます。大きなところでは、変数や関数名称の重複です。

このような場合、PHP のクラスの機能を名前空間として使い処理をまとめると、グローバルに対する干渉を最小限に抑えることが出来ます。(またオブジェクト指向をつかったトリックも使えるようになります)

先のグローバル関数をひとつ使ったアクションフックの定義をクラスを使ったものに変更すると次のようになります。

class Otenki {
    function prepare($post_ID, $post) {
        die("test");
    }
}

$otenki = new Otenki();
add_action('publish_post', array($otenki, 'prepare'), 10, 2);

クラスを定義(class Otenki {})し、その下で $otenki 変数に Otenki オブジェクトのインスタンス内を格納します。

$otenki = new Otenki();

クラス内に移動したメソッド(function prepare() { } )を add_action するには、’prepare’ と関数名のみを指定していた引数を、 array($otenki, ‘prepare’) と変更しインスタンスを特定できるようにします。

add_action('publish_post', /*ここ→*/array($otenki, 'prepare'), 10, 2);

以上の変更でクラス Otenki が名前空間代わりになります。クラス内部に実装するメソッドや定数・変数は、他との名前の重複など考えず自由に定義できます。

なお、クラス名 Otenki とそのインスタンスを格納する $otenki 変数は依然としてグローバルです。この部分だけ他との干渉を意識します。

(WordPress とのインターフェースになるクラスはいろいろな機能が実装されてしまうためクラス名には悩みますが、、SI業界的(?)には OtenkiPluginFaçade とかでしょうか。モジュール分割の規模によって決めると良いと思います。今回は処理が小さくひとつのクラスで済みそうなので単純に Otenki としました)

では「お天気情報の取得」と「テンプレートファイルからお天気情報を出力する」2つのメソッドを実装します。 まずはソースの骨組みのみ。

class Otenki {

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

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

    /**
     * 記事公開フックで天気情報を取得をする.
     *
     * @param $post_ID
     * @param $post
     * @return none
     */
    function prepare($post_ID, $post) {
        // お天気 API を呼び出し
        // Otenki::META_KEY, Otenki::LWWS_V1_JSON を用いて
        // カスタムフィールドに登録
    }

   /**
    * カスタムフィールドに取得したお天気アイコンを出力.
    *
    * @param none
    * @return none
    */
    function output() {
        // Otenki::META_KEY, Otenki::LWWS_V1_JSON を用いて
        // カスタムフィールドを取得しアイコン HTML を出力
    }
}

$otenki = new Otenki();
add_action('publish_post', array($otenki, 'prepare'), 10, 2);

class 内に prepare と output メソッドが実装され、共通するカスタムフィールドのキーなどが const(定数)で定義されていることが分かります。処理の class 化を行うと、このような共通項がグローバルへの影響無しに自由に実装できるようになります。

さて、アクションフックにより「お天気情報の取得」を担う prepare メソッドの呼び出しはうまくいきそうですが、テンプレートファイルにお天気情報を出力する output メソッドはどのように呼び出せばいいかという疑問がわいてきます。

これは、Otenki クラスのインスタンスが格納されている $otenki 変数がグローバルスコープであることを利用して、テーマのテンプレートファイル内で次のように呼び出すことができます。

$otenki->output();

ただ WordPress のテンプレートタグ的には、オブジェクト指向のメタファーが入ってしまうため、若干違和感があります。WordPress 本体では似たケースで次のような実装がされています。

テンプレートのループで使われる the_post() 関数。

wp-includes/query.php

/**
 * Iterate the post index in the loop.
 *
 * @see WP_Query::the_post()
 * @since 1.5.0
 * @uses $wp_query
 */
function the_post() {
    global $wp_query;

    $wp_query->the_post();
}

グローバルスコープののインスタンスが格納されている変数を関数に持ってきてメソッドを呼び出す、ヘルパー関数を準備しているわけです。

今回のケースも同様にヘルパー関数をつくってあげれば良さそうです。

/**
 * お天気情報を出力するテンプレートタグ.
 */
function the_otenki() {
    global $otenki;

    $otenki->output();
}

以上の、クラス定義、アクションフックの登録、ヘルパー関数がかかれたプラグインファイルが otenki.php とすると、動作シーケンスの概要は次のようになります。

otenki_seq

本来は記事の公開(do_action(‘publish_post’))とテーマ出力処理は同時には起きえませんが、この図では一緒に書いております。逆にこの動きを理解することで、1アクセス内で発生する順次処理の結果を、クラスのインスタンスに保持し、合成した結果を出すような処理もつくることができるでしょう。

というわけで、なかなかプラグインが完成しませんが、、また明日。 🙂


目次

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

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

WordPress 徹底解析(アクションフックのプラグインをつくる編)

「WordPress 徹底解析」シリーズです。

異常に忙しくなったり体の調子を悪くしてみたりで、できていなくてごめんなさい。がんばろう。というわけで、お久しぶりです!

なので脈略がなくなってきましたが「アクションフックのプラグインをつくる編」やってみまます。 🙂


アクションフック

前回書きました「WordPress 徹底解析(カスタマイズとフィルターとアクション編)」からアクショフックの実例になります。

WordPress 徹底解析(カスタマイズとフィルターとアクション編)

アクションの代表例はテンプレートファイルに記述する wp_head() や wp_footer() です。 これらを記述した部分にコアやプラグインから必要な HTML などが出力されているのはアクションフックの力です。 wp_head や wp_footer が動作したタイミングに合わせて、それぞれの処理が HTML タグを出力しています。

今回は、wp-otenki というプラグインのソースコードを元にアクションフックを解説してみようと思います。

これは、記事の公開時にライブドアの「お天気Webサービス(LWWS)」に問い合わせを行い、記事の書いた日にお天気情報を付与するプラグインです。

実はこのプラグイン 2006年に初版がかかれたもので、このたびついにライブドア側の API の変更で動かなくなったというお知らせを頂き、せっかくですので近代 WordPress に合わせて完全リライトしてみることにした次第です。(wp-cron がまだなくて自前で fsockopen してバックグラウンド処理していたくらい古かったです・・・)

プラグインの仕様は以下のようになります。

  • 「記事の公開時」にその日のお天気を LWWS API に問い合わせカスタムフィールドに格納する。
  • 格納されたお天気情報をテンプレートファイルの記述でアイコン出力する。

「記事の公開時」というタイミング。タイミングへの処理の追加といえばアクションフックの登場です。

アクションフックを用いると、さまざまな WordPress の動作(アクション)に対して、プラグインやテーマの functions.php に定義したコールバック関数(フック)で独自の追加処理を行うことが出来ます。

(フック)と注釈を書く場所に迷ってしまいましたが、、なんだろう、、「引っかける」という意味合いでしょうか。「デバッガを引っかける」とかぼくらの世界でよく言いますが、外部から中身(ここでは WordPress 本体)をいじらずに処理を乗っ取るとか追加するとかそういうイメージの言葉だと思います。

WordPress ではおおよそ考えられるほとんどの動作にフックができるようになっており、たとえば、<head>の出力時、WordPress の初期化終了時、テンプレートファイルへの処理移動時、記事保存前、記事保存時、管理画面の一覧カラム表示時、、等々等々・・・。いくらでも思いつきます。

どのようなアクションフックがあるかは codex から知ることができます。

プラグイン API/アクションフック一覧

この記事は(おそらくほぼ完全な)アクションフックの一覧です。アクションフックはバージョン2.1以降から、プラグインおよびテーマ開発時に利用できるようになっています。詳しい情報については下記もご覧下さい。

さて、アクションに処理(関数)を定義するには、WordPressの API 関数「add_action」を用います。また、直接使う機会は少ないかもしれませんが、その呼び出し元となる関数は「do_action」です。

add_action 関数に指定する引数のアクション名で、どの場合の動作に処理の追加を行うのかが決定されます。

フックはプラグインや functions.php での拡張向け機能として存在しているだけではなく、 WordPress 本体の機能でも数多く使われいます。

たとえば、サイトの <head> 部分に主に HTML を挿入する働きを担うアクション名「wp_head」フックだけに注目すると WordPress は以下のようなシーケンスで動作します。

action_hook_new

図中、add_action の 3引数目は関数呼出し順の優先度(プライオリティー)になります。同一アクション名に複数の関数が登録されている場合は、この値が少ない方から呼び出しが開始され、無指定の場合は 10 という値が使われます。

関数2、関数3 が登録順ではなくプライオリティー順で呼び出されていることに注目してください。処理順で不具合が起きる場合は、この値を調整することで呼出し順をコントロールすることができます。

ユーザがフックを活用する場合は、プラグインや functions.php に処理を行う関数を定義し、事前に add_action を用いてその関数を追加する、というのが大きなつくりかたの流れです。あとは、そのタイミングになれば自動的に WordPress が do_action を契機に登録された関数を呼び出してくれます。

では wp_head アクションフックの場合、契機となる do_action(‘wp_head’); を呼出してしているのは WordPress 本体のどこかを調査してみます。

wp-includes/general-template.php

/**
 * Fire the wp_head action
 *
 * @since 1.2.0
 * @uses do_action() Calls 'wp_head' hook.
 */
function wp_head() {
    do_action('wp_head');
}

wp_head() テンプレートタグ。

[tegaki]欧米か![/tegaki]

・・・言ってみたかっただけです。すいません。。

ということで wp_head アクションフックは、単純にテンプレートファイルの header.php にかくことになっている wp_head() テンプレートタグが do_action を呼び出しているのでした。

この場合は自分で do_action 呼び出しているのに近い感じですが、、多くのフックは WordPress によってひっそりと呼ばれております。

記事公開のフック

さて、今回つくりたいプラグインは「記事公開時」に「お天気取得処理」を追加したいということで、codex からそれっぽいアクション名を探します。

このような場合は、管理画面から公開ボタンを押した、ドラフト状態の記事を更新ボタンから公開した、xmlrpc 経由で記事を投稿した等に呼び出されれる「publish_post」 アクションが使えそうです。

処理を追加したいアクション名が分かれば、実装開始です。

プラグインファイル(もしくは functions.php)に以下の記述をしてみましょう。(プラグインヘッダコメントは省略しています)

function prepare($post_ID, $post) {
    die("test");
}

add_action('publish_post', 'prepare', 10, 2);

prepare は任意の関数名で、function で定義した関数を add_action(‘publish_post’, ‘prepare’ として登録しています。

とりあえずこれで、管理画面から公開ボタンを押すと「test」という真っ白画面が登場してくると思います。処理をのっとった!、、勝利の確信の瞬間です(笑) die(); しているので、思ったタイミングで落ちれば正解です。まずは関数が呼ばれることを確認するのがアクションフックをかく第一歩です。

add_action('publish_post', 'prepare', /*ここ→*/10, 2);

ソースコード中、add_action 関数の3番目引数(10)が先ほどでてきた同一フックに対する実行順を決定するプライオリティーです。今回のプラグインはいつ呼ばれても良くデフォルト値(10)で本来は省略できるのですが、、次の引数(2)を指定したかったのでしょうがなく書いています。

add_action('publish_post', 'prepare', 10, /*ここ→*/2);

問題の4番目引数(2)は、登録した関数に渡される引数の数になります。「prepare($post_ID, $post)」の個数である 2 が該当します。デフォルトは1で、指定しない場合は $post_ID だけ渡されるイメージです。

function prepare(/*ここと→*/$post_ID, /*ここ→*/$post) {

アクションフックの中にはこのように、関数の呼び出しとともに、さらに詳細な情報を引数で渡してくれるアクションもあります。 publish_post では、どの記事が公開されたかという情報を引数の $post_ID(記事ID) と $post(記事オブジェクト) で得ることができます。

このような引数をどのように調査すればいいかといえば、ソースコードをみるのが早くて正確です。ここまで読んでいただいた方ならお察しの通り、do_action( ‘publish_post’ で WordPress 内のファイルを検索すればでてくるってのが正解!なのですが、publish_post には若干のトリックがあります。

wp-includes/post.php

function wp_publish_post( $post ) {
    // 略
    wp_transition_post_status( 'publish', $old_status, $post );
    // 略
}

function wp_transition_post_status($new_status, $old_status, $post) {
    do_action('transition_post_status', $new_status, $old_status, $post);
    do_action("{$old_status}_to_{$new_status}", $post);
    do_action("{$new_status}_{$post->post_type}", $post->ID, $post);
}

do_action は、一番下の行「do_action(“{$new_status}_{$post->post_type}”, $post->ID, $post)」が該当します。残念ながら、変数で動的にアクション名をつくっているので、検索ででてこないのでした。

do_action("{$new_status}_{$post->post_type}", $post->ID, $post);

呼び出し元 wp_publish_post 関数。

wp_transition_post_status( 'publish', $old_status, $post );

第一引数が publish で、記事の投稿タイプは post なので、wp_transition_post_status 関数内の do_action で結合して publish_post となります。後ろに「$post->ID, $post」が続き、フックに登録した関数で情報が取得できることが分かります。

do_action("{$new_status}_{$post->post_type}", $post->ID, $post);

このようにソースコードをみることで、ついでに publish_page や publish_[カスタム投稿タイプ名]や、publish -> draft などのステータスの変更などもフックで「引っかけられる」ことも学ぶことが出来ました。:)

do_action('transition_post_status', $new_status, $old_status, $post);
do_action("{$old_status}_to_{$new_status}", $post);

この辺は、WordPress を少しやってくれば経験的に「こうなっているんではないか」という予測がつくようになってきます。WordPress の恐るべきは、それが大抵予測通りなところです。IDE などの関数呼び出し元ジャンプ機能を使ってみていけば、すぐお望みのものが見つかるハズです。

てなわけで、再度プラグインのソースコードに戻ります。

function prepare($post_ID, $post) {
    die("test");
}

add_action('publish_post', 'prepare', 10, 2);

この処理は、PHP 5.3 の無名関数を使うと次のようにもかくことができます。関数名削除。

add_action('publish_post', function($post_ID, $post) {
    die("test");
}, 10, 2);

jQuery みたいで便利ですね。

関数名考えなくて済みますので、PHP 5.3 が使える環境の方は是非。プラグインや functions.php で定義する関数はグローバル領域となってしまいますので、関数名かぶりを考えないと意味でも楽ができます。 🙂

さて、ここまでいけば後はお天気 API に問い合わせをして、カスタムフィールドに格納する処理を関数に書けば良いです。

add_action('publish_post', function($post_ID, $post) {
    // "今日"のポストの時だけ天気を取得
    if(date("Y/m/d", strtotime($post->post_date)) != date("Y/m/d")) return;
    // LWWS(V1/JSON)より天気情報(札幌)を取得
    $json = file_get_contents(
        'http://weather.livedoor.com/forecast/webservice/json/v1?'
        . http_build_query(array('city' => '016010')));
    // 正常な JSON 形式であればカスタムフィールドに格納
    // 取得できなかった場合は次回の公開に期待
    if($json !== false && json_decode($json) !== null) {
        update_post_meta(
            $post_ID
            , 'otenki'
            , $json);
    }
}, 10, 2);

API からもらえるお天気情報は過去日が取得できず現在日以降になります。処理上のポイントは「”今日”のポストの時だけ天気を取得」の部分で、アクションフックからもらった $post 記事オブジェクトの情報を元に「今日」日付の公開記事のみを取得するようにしています。過去日の公開では天気は取得しません。

余談ですが WordPress の未来日の予約投稿は、実際にその日になった時に公開処理が行われます。これは WordPress に内蔵されるスケジューラの力によるものです。

さて、ここまでつくってみたものの、このままではプラグインの体をなしていません。札幌しかお天気とれないし、、サイト用にカスタムフィールドから HTML タグを出力する処理もありません。また、記事の公開と同じシーケンスで外部 API に対して通信取得処理を行っているため、若干公開処理が遅くなると言う弊害もあります。

というわけで、次回(明日予定)はこれらの対応と、プラグインの class 化によるクリーンナップや、管理画面をつけてみたりしたいと思います。 お楽しみに!

忘れていました。。すいません。。すいません、、こちらも次回に。 🙂


目次

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

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