WireframeSketcher でワイヤーフレームドキュメント作成

以前、サイトを眺めてすごいなぁと思っていたものの、有料($99)だったため試さなかった、WireframeSketcher を本日購入してみました。

用意された部品や操作系を元に、アプリケーション開発時のワイヤーフレームやモックアップなどのドキュメント作成を支援するソフトウェアです。 SI の世界だと、エクセル方眼紙でやっちゃうような、外設の画面設計書をつくるの、で通じるでしょうか(笑)

WireframeSketcher

WireframeSketcher is a wireframing tool that helps designers, developers and product managers quickly create wireframes, mockups and prototypes for desktop, web and mobile applications. It’s a desktop app and a plug-in for any Eclipse IDE.

デザイナー、開発者、プロダクトマネージャさん向け。

Eclipse GEF/EMF ベースでつくられており、Windows、Mac、Linux 対応。ダウンロードしすぐ実行できるオールインワンのスタンドアローン版の他、Eclipse プラグイン版も用意されています。

130422-0007

下のような、描くのも変更するのも大変そうなイラストが、

wp_tree_f

次のテキスト入力だけで作成できてしまう。。

wp-content
-plugins
--otenki
---otenki.php
-themes
--[v] twentyeleven
--[v] twentytwelve

なんてことだ...

このことだけで、なんでもっと早く試さなかったのだろうと後悔です。。15日のお試し期間中の 1 日目で購入してしまいました。

以下、これまたどう考えても描くの面倒だろうJK、と言われそうな一覧表とフォームの組み合わせも、

130422-0019

こんな感じのテキストでサクッとつくることができます。

130422-0020

あっという間。。 🙂

試しに、このサイト hiromasa.another のモックアップを作成してみました。

130422-0011

出力を手書き風味にできるのがポイントで、(お客さんなどに)まだデザインとかはイメージですよ、というようなことを表現できるとのことです。

ちなみに Screen のプロパティーを Sketch から Clear に設定することで、びしっとすることもできます。

130422-0002

びし。まっすぐ。

130422-0010

WireframeSketcher の操作はパレットビューより部品を選択し、ぺたぺた貼り付け、それぞれの個別属性を指定していく感じです。

部品は PC 用、モバイル、Android、iOS、Web フォームなどなど沢山。 これらは一般的な .svg 形式で、フォーマットに従えば自分で描いた絵も部品に取り込むことができるようです。

130422-0021

ブログのコメントフォームを描いてみたの図。

130422-0022

ドローアプリとしての操作感は、利用開始当初、レイヤー機能がないため若干厳しい部分あるかなと思いましたが、アウトラインビューなどからのZオーダ設定、グルーピング系と、そのグループへの突入(?)、またプロパティーからの位置ロック操作をうまく使えば、問題なく描くことができると感じました。

130422-0023

画面上、オブジェクトの単一選択と矩形選択の切り替えの UI がぱっと分かりませんが、(Fn + ) F3/F4 でそれぞれに切り替わります。クリック to ドラッグの範囲選択において、カーソル位置のオブジェクトを引っ張りたくないケースでは F4 を押すといいと思います。

さて、WireframeSketcher の良いところのひとつは保存形式に XML のテキスト形式が用いられていることで、さらに画像となる .svg ベクターデータと、骨組みとなる .screen データは完全に分離されています。

このため、骨組みの .screen の変更履歴をみると、どの部分が修正されたかが分かります。

130422-0016

スクリーンショットは新旧ファイル差分を出した図。–File Three が増えてその下のアイテムの y 座標が増えたことが分かります。

このように、WireframeSketcher はテキストファイルベースのデータ形式を採っているため、ローカルヒストリーの利用や Subversion、Git などバージョン管理との統合も得意です。

Eclipse マーケットから EGit や Subclipse などを追加することで Git や SVN が使えるようになるハズです。(スタンドアローン版 は Eclipse Juno ベース。自分は Eclipse Juno for JavaEE に WireframeSketcher をプラグインの形で入れました)

さて、ぼくは普段からドキュメントの作成に Eclipse Mylyn の .textile サポートを使っていましたので、今回ワイヤーフレーム作成に WireframeSketche という心強い見方が統合されたことになります。

ワイヤーフレームはもちろん、ちょっと億劫だなと思っていた図形の描画もできるようになってとても嬉しいです。 🙂

130422-0018

ブログ”徹底解析”シリーズを Eclipse で書いているの図。

日本に一人しかいないんじゃないかと思うくらいの Eclipse の利用法ですが、ソースコード見ながら記事を書くことが多いので、この使い方はすごく便利です。(ちなみに 300ページの原稿に耐えてます)

上記スクリーンショット下部は、PlantUML。これもテキストベース図形描画 & Eclipse プラグインあり。 UML を描くツールですが、これがまた色々な用途に使えるのです。

PlantUML についてはプログラマーズ雑記帳さんが非常に参考になります。(とても助かりました!)

PlantUML の使い方 | プログラマーズ雑記帳

テキストから UML を生成する PlantUML についての解説記事を書いてみました。

PlantUML の Eclipse プラグインについては、そのうちまた。 🙂

WordPress for Android 更新

WordPress for Android がアップデートしてバージョン 2.3 になっていましたので、アンドロイドタブレットと普通(?)のスマートフォンで試してみました。

今時はサイト更新をスマートフォンでやりたいという要件も増えている気もしますので、研究に触ってみるといいかもしれません。 🙂

ここで使っているのは ICONIA TAB A700 と Bluetooth 接続キーボード & GALAXY SII です。 ギャラクシーで撮影したので、自分自身の画像はなし。。

wpid-wp-1366332913417.jpg

今回のアップデートから上の写真のようにタブレットを横置きで実行した場合でも、画面スペースが有効活用されるようになり見やすくなっています。(サイドメニューがでるようになりました)

wpid-Screenshot_2013-04-19-13-15-21.png

新規投稿は右上の+ボタンです。投稿までの操作が少なく便利になりました。 🙂

普通のスマートフォンで縦画面表示だと次のような感じになります。 左から操作メニューがひょこっとでてきます。

SC20130419-161934

このようなブログ投稿アプリは、投稿にネットワークを使わないローカル編集ができるのが良いところのひとつ。WordPress for Android では編集画面の「状態」を「ローカル下書き」することで途中保存が可能です。

wpid-Screenshot_2013-04-19-13-41-38.png

ここの状態を「下書き」にするとサーバ側のいつもの下書きになりますので、途中から PC で引き続きといったこともできます。

画像を含めた投稿の編集については、「ローカル下書き」まではエディターに画像プレビューが表示され、「下書き」を含むサーバ保存後の再編集はイメージタグの状態となります。

ローカル編集中。

SC20130419-164342

サーバ保存後。

SC20130419-164600

普段 HTML を扱わない方にはやや分かりづらいですが、サーバ保存後に  wpautop 形式や HTML になったデータをプレビューしながら編集するのはかなり難しい処理ですので、ここはしょうがない仕様です。

Masayan「・・・」

画像に関しては残念ながら「本文に入れずに画像(メディア)だけアップロードする」処理はできません。これは、WordPress の Web 管理画面のようにメディアライブラリのみの登録を行う UI がないためです。

ただし、アイキャッチ画像の設定に関してはこの操作が可能で、挿入するしている画像の長押し操作からでてくる「画像の設定」画面で「アイキャッチ画像として使用」を設定した場合、「投稿本文中に画像を含める」の選択を使うことが出来ます。

SC20130419-171222

さて、WordPress デザインワークブックのサンプルサイトとなり、関係者一同、写真を見ておなかがすいてしまい制作が進まなかったことがあると噂の、札幌の nino クレープさんのブログも WordPress for Android で更新されております。 写真とコメント付きでクレープが載り、ついつい行きたくなりますゆえ。

スマートフォンは身近にあり気軽に使えますので、画像の扱いや操作系など研究の上、WordPress サイト更新の選択肢の一つとして検討してみると良いかもしれません。 iOS 版ももちろんあります。

これらの WordPress クライアントで使われている xmlrpc インターフェースは、自分でも利用することができます。 サイトに合わせた特別なアプリをつくるのも便利そうですね。 いつかやってみたいです。 🙂

WordPress 徹底解析(まとめ)

ブログにて 6日間に渡り、WordPress 拡張機能の実装例を紹介しながら、フックの動作や内部 API、そしてそれらを骨組みする方法を解説してきました。

このページは最後のまとめと目次となります。

連載はブログ記事としてひといきで書いたもので、本来であればここから沢山のリライトをしたいところですが、まずは一度パブリッシュしております。 書き足りない部分、不要なギャグ(!)、言葉やソースコードの揺れが見られますが、推敲は別な機会にということでどうかお許しください。


はじめに

WordPress を使ったサイト構築の手法の中で必要となってくる、フックや内部 API などを用いた機能の拡張方法と、そのプログラムの構成方法を解説します。

紹介しているサンプルソースコードは WordPress のプラグイン形式、もしくはテーマの functions.php の双方で使うことができます。つまり内容は、WordPress のプラグイン及びテーマ開発者にとって有効です。

記事には個別の実装法だけではなく、 サイト構築において一般的な拡張を行おうとした場合に取り得る WordPress の全体的な動作シーケンスの解説が含まれ、内容は汎用的に利用することができると考えています。

WordPress を使った開発で、この連載が少しでも役に立てば幸いです。


Otenki について

本連載でサンプルとしている「Otenki」は、気象情報を提供する LWWSLivedoor Weather Web Service) API から、記事投稿当日のお天気情報の取得・出力を行う WordPress の拡張プログラムです。

大きく次の機能を持っています。

  • 投稿時のお天気情報の自動取得
  • 取得したお天気のテーマへの出力
  • 取得対象となる地域の管理画面からの指定

otenki_post

otenki_admin

含まれる実装的要素は以下のようになります。

  • アクションフック
  • 非同期スケジュール登録
  • 外部 API への http アクセス
  • 管理画面

解説は個々の機能だけではなく、WordPress において拡張の実装を行うためのプログラム構成例や考え方も同じくらいのボリュームでしています。その他の拡張機能やテーマの実装を行う場合においても、知識のひとつとして使えるはずです。


ソースコード

最終形のソースコードは次のリンクから閲覧することができ、実際に WordPress サイトで動作させることができます。

otenki.php

本ファイルの内容を otenki.php として保存し、プラグインディレクトリに格納した上で有効化することで動作します。また、ファイルからプラグインヘッダを除くソースコードをコピーし、テーマの functions.php に貼り付けることでも動作させることができます。

拡張の動作開始後、お天気情報を取得する地域を指定するため、「管理画面->投稿設定->お天気情報取得サービス」から地域IDを指定し「変更を保存」ボタンを押下してください。

お天気情報のテーマへの出力は、テンプレートファイルのループ内の任意の位置で次のように do_action(‘the_otenki’); をすることで行うことができます。

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

この後、記事の投稿を行うとお天気情報が取得され、指定した位置にお天気情報が出力されます。

なお、サンプルに下位互換の機能を追加した正式版の WordPressプラグインアーカイブは次から取得できます。

WordPress Plugins/JSeries » wp-otenki


目次

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

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

まとめ

WordPress のサイト構築で必要となってくる知識は大きく3つあると考えます。

  1. WordPress API
  2. HTML/CSS や http・データベースなどシステムを構成する環境
  3. PHP プログラミング言語のボキャブラリ

この 3つの知識を揃えた時点でスタートラインとなるため、何も分からない状態であると取りかかるまでの時間が少しかかります。

本連載ではこれらをまんべんなく、既知であろう部分はギリギリまで省いて解説しました。恐らく最初に指針が欲しいのはソースコードの構成や動作シーケンスの組み立てだと考え、これらを中心において書き進めています。

Web を含むオープンシステムは、開発に必要とされる知識が広範囲にわたります。しかしながら WordPress 上での作業は、WordPress に備わる柔軟な API やフレームワーク的な動きが、多くの面倒な部分や難解な部分を隠してくれるため、集中して楽しむことができるのではないかと思っています。

ソフトウェアの開発は非常に面白いものです。WordPress を通じてプログラミングが初めての方が動く感動を知らせてくれた時は、自分のことのように嬉しいと感じます。実はそんな想いも込めて、共著しております「WordPress デザインワークブック」も書きました。もしこの連載がまだ少し難しいと感じた時は手に取ってみてください。

Code is Poetry.

WordPress の世界にようこそ。

WordPress 徹底解析(WordPressを拡張するプログラムの構成法 – 管理画面をつける)

作成した拡張の設定値を、ユーザが WordPress の管理画面から操作できるようにします。

WordPress には、既存の設定ページに項目を簡単に追加できる便利な Settings API があり、これを用いて地域IDを「投稿設定」画面から設定できるようにしてみましょう。

また、完成したプラグインを機能の明確化のためにリファクタリングを行い、最後の仕上げをしてみます。


Settings API による管理画面付与

前回までのお天気取得拡張の機能で取得するお天気の地域を変更するには、ソースコードを修正しコンストラクタ引数により地域IDを設定するしかありませんでした。

今回は、これをユーザが画面から設定できるようになる管理画面の追加を行ってみます。

このような簡単な項目追加の場合、既存の管理画面のページに設定フォームを追加する Settings API と呼ばれる管理画面用 API を用いると、実装も楽で、ユーザも分かりやすいでしょう。

使い方はいくつかの API 関数の呼び出しとフックの登録で実現できます。

登場するのは、

  • セクションの追加(add_settings_section)
  • 項目の追加(add_settings_field)
  • 項目登録(register_setting)

の3関数と、これらの呼び出しを実装したメソッドです。

この実装メソッドを admin_init アクションフックに登録するという手順を踏むことにより、管理画面への画面展開やオプション値の保存が自動的に行われるようになります。

実装メソッド名を setting とし、処理を抜粋した概念ソースコードは次のようになります。

class Otenki {
    /**
     * オプション値・キー.
     */
    const OPTION_KEY = 'otenki';

    /**
     * 管理画面の投稿設定に地域ID設定を追加.
     */
    function setting() {
        // 投稿設定画面
        $writing = 'writing';
        // 投稿設定にセクション追加
        add_settings_section(
            'otenki_setting_section'
            , 'お天気情報取得サービス'
            , array($this, 'echoSettingSection')
            , $writing);
        // セクションに地域ID指定テキストボックス追加
        add_settings_field(
            self::OPTION_KEY
            , '地域ID'
            , array($this, 'echoSettingField')
            , $writing
            , 'otenki_setting_section');
        // 設定に登録
        register_setting($writing, self::OPTION_KEY);
    }

    /**
     * セクション文字列出力.
     */
    function echoSettingSection() {
        echo '取得したい地域IDを指定してください。';
    }

    /**
     * 地域ID設定テキストボックス出力.
     */
    function echoSettingField() {
        echo '<input name="' . self::OPTION_KEY . '"'
        . ' id="' . self::OPTION_KEY . '"'
        . ' type="text"'
        . ' value="'
        . esc_attr(get_option(self::OPTION_KEY)) . '" />';
    }
}

$otenki = new Otenki();
add_action('admin_init', array($otenki, 'setting'));

管理画面「設定->投稿設定」に HTML が展開され結果は次のようになります。(赤部分)

otenki_admin

ソースコードを順に見ていきます。

まず、add_action により Otenki クラスの setting メソッドを登録します。

add_action('admin_init', array($otenki, 'setting'));

setting メソッド内で管理画面に対するセクション、項目の登録を行います。

function setting() {
    // 投稿設定画面
    $writing = 'writing';
    // 投稿設定にセクション追加
    add_settings_section(
        'otenki_setting_section'
        , 'お天気情報取得サービス'
        , array($this, 'echoSettingSection')
        , $writing);
    // セクションに地域ID指定テキストボックス追加
    add_settings_field(
        self::OPTION_KEY
        , '地域ID'
        , array($this, 'echoSettingField')
        , $writing
        , 'otenki_setting_section');
    // 設定に登録
    register_setting($writing, self::OPTION_KEY);
}

add_settings_section、add_settings_field 関数の引数はコールバック関数を持ち、それぞれの画面出力用に登録したメソッド(echoSettingSection、echoSettingField)から HTML を行います。

PHP 5.3 以降が使える環境であればメソッドを登録せず無名関数で次のようにも記述できます。利用できる環境であればこちらのほうが良いでしょう。(無名関数を用いない場合、コールバックに登録するメソッドは public にする必要があります)

// 投稿設定にセクション追加
add_settings_section(
    'otenki_setting_section'
    , 'お天気情報取得サービス'
    , function() {
        echo '取得したい地域IDを指定してください。';
    }
    , $writing);
add_settings_field(
    self::OPTION_KEY
    , '地域ID'
    , function() {
        echo '<input name="' . self::OPTION_KEY . '"'
        . ' id="' . self::OPTION_KEY . '"'
        . ' type="text"'
        . ' value="'
        . esc_attr(get_option(self::OPTION_KEY)) . '" />';
    }
    , $writing
    , 'otenki_setting_section');

echoSettingSection、echoSettingField、register_setting 各 API 関数で指定している ‘writing’ は投稿設定画面を指定する引数です。

指定できる設定ページには general(一般設定), reading(表示設定), writing(投稿設定), media(メディア)などが管理画面の「設定」ページと対応しており、今回は「投稿時」の地域IDの指定ということで、writing(投稿設定) を渡しています。

const OPTION_KEY で定義している ‘otenki’ がデータベースに格納される((wp_)options テーブル)のキーとなり、add_settings_field 関数の引数と、echoSettingField で出力している name、id 属性を一致させていることにも注目してください。この対応により、自前で管理画面の処理を実装することなく、画面が展開されデータベースに値が保存がされるようになります。

以上で設定が管理画面から保持できるようになりましたので、この地域IDを元に Otenki クラスを動作させるようにします。コンストラクタで一定となっている地域IDを、保持した設定値で上書きするように修正します。

/**
 * コンストラクタ・プラグインデフォルト値設定及び設定の取得.
 */
function __construct(
    $city = '016010', $css_class = 'otenki') {
    // LWWS URL 引数(地域別値)
    $this->city = $city;
    // 画像出力時の CSS クラス
    $this->css_class = $css_class;
    // 管理画面地域ID設定を取得し存在すれば上書き
    $value = get_option(self::OPTION_KEY);
    if(!$value) {
        $this->city = $value;
    }
}

get_option で設定値を取得し、あればその地域IDを用いる流れです。管理画面未設定時はデフォルトコンストラクタの引数で動作します。

この処理をもって、管理画面から地域IDを指定し、その値を使って拡張が動作するようになりました。

リファクタリングとシングルトン

ここまででついに目標とするお天気情報取得の処理が完成しました。

ここからは実装上、気になる部分の修正をしていきましょう。このような動きを変えずに、ソースのクリーンナップや拡張を行うことをリファクタリングと呼びます。

まずはクラスと WordPress のインターフェースに注目した全体の概念ソースを示します。

class Otenki {

    /**
     * コンストラクタ・プラグインデフォルト値設定及び設定の取得.
     */
    private function __construct(
        $city = '016010', $css_class = 'otenki') {
        // 「オプション値・キー」を使った設定値取得
        // 「地域ID」設定
    }

    /**
     * 記事公開フックで天気情報取得スケジュールを作成する.
     */
    function prepare($post_ID, $post) {
    }

    /**
     * LWWS から天気情報を取得し記事のカスタムフィールドに格納する.
     */
    function extract($post_ID) {
        // 「地域ID設定」取得
        // 「カスタムフィールド・メタキー」を使ったカスタムフィールド値登録
    }

    /**
     * カスタムフィールドに取得したお天気アイコン用 HTML をアクションフックで出力.
     */
    function output() {
        // 「カスタムフィールド・メタキー」を使ったカスタムフィールド値表示
    }

    /**
     * 管理画面の投稿設定に地域ID設定を追加.
     */
    function setting() {
        // 「オプション値・キー」を使った設定
    }
}

$otenki = new Otenki();

add_action('publish_post', array($otenki, 'prepare'), 10, 2);
add_action('the_otenki', array($otenki, 'output'));
add_action('extract_otenki', array ($this, 'extract'));
add_action('admin_init', array($this, 'setting'));

ソース下部の記述により、Otenki クラスは「$otenki = new Otenki()」からインスタンスが作成され、各 add_action により WordPress にフックが登録されます。

ここで気になるのが new Otenki() のインスタンス作成部分です。このクラスは地域IDの異なる複数のインスタンスが作成できるにもかかわらず、次の理由で正しい処理が行えません。

    • 「カスタムフィールド・メタキー」が一定で、他のインスタンスから値が上書きされてしまう。
    • 管理画面「オプション値・キー」が一定で、複数の地域IDを指定することが出来ない。
    • 管理画面のテキストボックスの name や id が一定で、複数の地域IDの入力欄が出力できない。

これらを対応するためには、固定値となっている部分に地域IDを連結付与するなど地域をユニークにする実装が考えられます。

ただしいずれにしてもインスタンスを増やすにはソースコードを修正し

$sapporo = new Otenki();
$kurume = new Otenki('400040');

などとするしか方法がありません。

つまりクラスを用いて全ての実装を行うには、Otenki クラスのインスタンスの生成を束ねるコントローラとなるもうひとつのクラスが必要になります。

$otenki = new OtenkiController();

コントローラクラスは、管理画面の設定やそのオプション値をもって複数の Otenki クラスの生成を行う、WordPress 内で唯一のインスタンスとなります。

もし仮にこのクラスが別なモジュールから new され複数のインスタンスが作成された場合は、管理画面やオプション値がおかしなことになるでしょう。 WordPress はひとつなので、対話を行うインスタンスもひとつといった意味合いです。

このように、ただひとつだけインスタンスの存在を許すクラスのことをシングルトンクラスと呼び、実装のテクニックにより唯一のインスタンスを確保することができます。

その実装の一部は次のようになります。インスタンスの取得を行うために new をしていないところに注目してください。

$otenki = OtenkiController::getInstance();

さて、このような複数のインスタンスを束ねるコントローラクラスの存在ですが、このお天気取得プラグインにはいささかオーバスペックです。複数の地域IDを扱える可能性を持たせてクラスをつくってあるものの、実にまだそういった要件がないためです。

しかしながら完成している Otenki クラスに、シングルトン性を確保しておくことには意味があることでしょう。他の開発者が別なモジュールから Otenki インスタンスを作成する可能性はほぼゼロではありますが、シングルトン実装を行っておくことで動作をソースコード上で明示化できます。

ではここからは Otenki クラスのシングルトン化を含むリファクタリングを行い、自分を含む開発者にソースコードを持って意思を伝達するテクニックを紹介していきます。

まずは、ここまで解説した「このクラスのインスタンスは唯一ひとつしか存在できない」というメッセージを表現するために、シングルトンパターンと呼ばれる実装を Otenki クラスに加えていきます。

最初に、Otenki コンストラクタを public から private に落とします。これで外部からのインスタンスの作成(new)が言語構文上不可能になります。

private function __construct(
    $city = '016010', $css_class = 'otenki')

次に、static メンバ変数とメソッドを使い、Ontenki クラスの new を一度だけしか行えないようにブロックし、インスタンスをひとつに保証します。まとめると次のようになります。

class Otenki {
    /**
     * シングルトンインスタンス格納.
     */
    static private $INSTANCE = null;

    /**
     * WordPress 拡張処理用 Otenki インスタンスの取得.
     * 
     * @return Otenki
     */
    static function getInstance() {
        if(self::$INSTANCE == null) {
            self::$INSTANCE = new Otenki();
        }
        return self::$INSTANCE;
    }

    /**
     * コンストラクタ・プラグインデフォルト値設定及び設定の取得.
     */
    private function __construct(
        $city = '016010', $css_class = 'otenki') {
        /* 省略 */
    }
}

この実装により Oteki インスタンスを得るには Otenki::getInstance() の記述が唯一の方法となり、返却されるインスタンスは常に同じ、ひとつに保証されます。

ちなみに、本来はシングルトンインスタンスを作成・返却する部分を、

/**
 * シングルトンインスタンス格納.
 */
static private $INSTANCE = new Otenki();

/**
 * WordPress 拡張処理用 Otenki インスタンスの取得.
 * 
 * @return Otenki
 */
static function getInstance() {
    return self::$INSTANCE;
}

と実装したいところなのですが、PHP の場合、

static private $INSTANCE = new Otenki();

とスタティックイニシャライザが使えないため、前述の方法で実装しています。(この実装はマルチスレッド環境では不具合を起こしますが、PHP は言語仕様がシングルスレッドモデルなので問題ないでしょう)

シングルトン化された Otenki をフックに登録します。

リファクタリング前のソースは以下のようになっていました。new を行いインスタンスの生成をしています。

$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'));
add_action('admin_init'
    , array($otenki, 'setting'));

リファクタリング後のソースは、new は行わずインスタンスは getInstance() メソッドから取得することになりましたので、次のようになります。

add_action('publish_post'
    , array(Otenki::getInstance(), 'prepare'), 10, 2);
add_action('extract_otenki'
    , array (Otenki::getInstance(), 'extract'), 10);
add_action('the_otenki'
    , array(Otenki::getInstance(), 'output'));
add_action('admin_init'
    , array(Otenki::getInstance(), 'setting'));

シングルトン化によりインスタンスはひとつしか存在できないと明示でき、副次的効果としてグローバル変数 $otenki を削除することができました。これで完全に WordPress と Otenki は 1:1 の関係となりました。

シングルトン化が完了しましたので、次にこの拡張がどのように WordPress インターフェースを使っているかをソースコード上に表現してみます。

ユーザ(開発者)が意識する WordPree とのインターフェース(フック)は、記事公開(publish_post)と、テンプレートタグにお天気を表示する(the_otenki)のふたつだけで、その他はインスタンス内部で使われているものです。

逆に言えば、スケジュール実行(extract_otenki)と管理画面(admin_init)は Otenki クラスとは内部構造上切っても切れない関係です。

このことを表現するため、開発者が意識するフックをクラスの外側に、切り離せないフックをコンストラクタで定義します。

class Otenki {
    /**
     * コンストラクタ・プラグインデフォルト値設定及び設定の取得.
     */
    private function __construct(
        $city = '016010', $css_class = 'otenki') {
        // 投稿スケジュール用アクションフック登録
        add_action('extract_otenki'
            , array ($this, 'extract'));
        // 管理画面アクションフック登録
        add_action('admin_init'
            , array($this, 'setting'));
    }
}

add_action('publish_post'
    , array(Otenki::getInstance(), 'prepare'), 10, 2);
add_action('the_otenki'
    , array(Otenki::getInstance(), 'output'));

このように明示的に記述を分けておくことで、開発者は publish_post と the_otenki は単純に外部からコントロールしてもよいと考えられるようになります。

たとえば、条件によって記事公開時のお天気情報の取得を行いたくない場合は、開発者は安心して自身のモジュールに次のような実装ができます。(plugins_loaded は全てのプラグインロード後に呼ばれるアクションフック、remove_action はアクションフックから特定のフックを削除する関数)

add_action('plugins_loaded', function() {
    if(/*なんとか条件 */ false) return;
    remove_action('publish_post'
        , array(Otenki::getInstance(), 'prepare')
        , 10)
});

また上記のソースからも分かるとおり、シングルトン化したことにより他のモジュールから拡張を扱う場合でも「Otenki::getInstance()」メソッドが利用できるようになり、$otenki などのグローバル変数を介した処理よりも、どのモジュールの処理なのかを明確にすることができます。

まとめ

管理画面の追加とリファクタリングが完了しました。

今回のポイントは、

  • 既存の設定ページに、簡単なオプション値の項目を追加するには Setting API が利用できる。
  • WordPress とのインターフェースを取り持つクラスでひとつしかインスタンスが存在できない実装は、シングルトンパターンを利用するとソースコードがメッセージ性をもち、他のモジュールからも利用しやすい。
  • フックを利用する場合は、そのインターフェースをソースコード上に表現し、他の開発者が操作しやすいようにしよう。

でした。

次回はいよいよシーズン2最終回。 総まとめ+目次です。 お楽しみに。 🙂


目次

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

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

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 を用いたスケジュール化手法を解説しています。
テンプレートファイルからの情報出力(この記事)
テンプレートファイルに処理を記述し出力する場合に、拡張モジュールと疎結合にする実装法を紹介します。アクションフックの活用例としても有効です。
管理画面をつける
管理画面に設定欄を設けユーザが拡張の機能をコントロールできるようにする手法の解説です。また、本連載中のソースコードに最終的なリファクタリングをかけ、他の開発者にこの拡張の意味を表現するプログラミングテクニックも解説します。
まとめ
本連載の概要とまとめ、そして目次です。

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

外部APIの呼び出しなど時間がかかる処理で、投稿などの動作が遅くならないよう、WordPress に備わる WP-Cron という仕組みを利用して、実行を非同期化するテクニックを紹介します。 🙂


お天気API呼び出しの非同期化

前回は、アクションフックを用いて投稿時にお天気APIに問い合わせを行い、カスタムフィールドにお天気情報を格納する部分の実装を行いました。

主要部分の概念ソースコードは次のようになります。

class Otenki {
    function prepare($post_ID, $post) {
        // "今日"のポストの時だけ天気を取得判定
        // wp_remote_get 関数による http リクエスト
        // カスタムフィールドへの情報格納
    }
}

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

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

otenki_seq3

今回注目するのは、上記黄色メモ部分「お天気APIの取得処理」と後続の処理の関係です。

APIの呼び出しには外的要因が多く、時間がかかったり予想外のデータが戻ってきたりする場合があります。自分の扱い知らぬところで動作するため、慎重な実装が必要です。

たとえば、ネットワークや対向 API サーバの都合で API 呼び出し処理に 60 秒かかってしまうとすると、後続がそれだけ遅れていきます。簡単なところでは投稿後、管理画面の再描画までにプラス 60 秒かかって可能性があるということです。これはちょっといただけません。

また、前回外部APIから取得した情報の処理にはしっかり正当性確認をしようという部分がありましたが、外的要因如何よっては PHP が Fatal Error で終了してしまうという不安もぬぐいきれません。 WordPress にはトランザクションロールバックの処理がありませんので、後続処理によってはデータが不整合してしまう可能性も出てきます。

もちろん、処理をなんとかひとつのトランザクションとして扱いたい場合はやむを得ないところなのですが、WordPress の場合主たる処理は記事の投稿です。今回のお天気のような付加情報は、また次回がんばろうという考え方ができます。

以上のような理由から、お天気APIの取得を記事投稿とは切り離した処理にしてしまおうというのが今回のお話です。このような実行の一部で、結果を待つこととなく処理を進めることを非同期処理と呼びます。

WP-Cron によるスケジュール

WordPress には決められた時間を元に、一定の処理を行う WP-Cron と呼ばれるスケジューラのが組み込まれています。たとえば、未来日に設定した投稿したが、その時間で公開されるのはこの機能の働きです。

また、WP-Cron で行う処理は、サイトや管理画面の表示とは別な流れで行われるようになっており、本筋の動作に影響がでません。これは本筋の処理から、バックグラウンドで動くスケジュール処理用の別な WordPress を起動する特殊な実装で動いているためで、次のように自分自身で WordPress に http リクエストを行い実行するようになっています。

/**
 * Send request to run cron through HTTP request that doesn't halt page loading.
 *
 * @since 2.1.0
 *
 * @return null Cron could not be spawned, because it is not needed to run.
 */
function spawn_cron( $gmt_time = 0 ) {

    /* 省略 */

    $cron_request = apply_filters( 'cron_request', array(
        'url' => site_url( 'wp-cron.php?doing_wp_cron=' . $doing_wp_cron ),
        'key' => $doing_wp_cron,
        'args' => array( 'timeout' => 0.01, 'blocking' => false, 'sslverify' => apply_filters( 'https_local_ssl_verify', true ) )
    ) );

    wp_remote_post( $cron_request['url'], $cron_request['args'] );

}

このような WP-Cron の特徴を生かすと、外部 API 取得処理のような時間のかかる可能性がある処理を非同期化することができます。WordPress 本体内でも更新 ping の送信や Akismet のスパムチェックなどで同様のテクニックが使われています。

では実際に WP-Cron を使ってお天気 API 取得処理の非同期化を行ってみます。

API がよくできていますので、WordPress でスケジュールをつくるのは難しくありません。ここではひとつだけスケジュールを作成する wp_schedule_single_event 関数を使います。

wp_schedule_single_event 関数は引数で指定した時刻以降に、指定したアクションフックを呼び出してくれます。

アクションフックは任意のものが指定できますので、お天気 API の取得を行うフックを “extract_otenki” と命名し、その処理がかかれた execute メソッドを登録(add_action)してあげます。

前回つくった prepare の取得処理部分だけが execute メソッドに移り、代わりに wp_schedule_single_event でスケジュール登録する形になります。

最初の概念ソースコードとの差分は次のようになります。

publish_post アクションフックだけで処理していた最初のソース。

class Otenki {
    function prepare($post_ID, $post) {
        // "今日"のポストの時だけ天気を取得判定
        // wp_remote_get 関数による http リクエスト
        // カスタムフィールドへの情報格納
    }
}

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

wp_schedule_single_event で処理を非同期化したソース。

class Otenki {
    function prepare($post_ID, $post) {
        // "今日"のポストの時だけ天気を取得判定
        // 天気取得の非同期スケジュール作成
        wp_schedule_single_event(
            time()
            , 'extract_otenki'
            , array($post_ID));
    }

    function extract($post_ID) {
        // wp_remote_get 関数による http リクエスト
        // カスタムフィールドへの情報格納
    }
}

$otenki = new Otenki();

add_action('publish_post'
    , array($otenki, 'prepare'), 10, 2);
add_action('extract_otenki'
    , array ($otenki, 'extract'), 10);

prepare メソッドと extract メソッドで処理対象となる記事IDが、wp_schedule_single_event の引数を介して受け渡しできている部分にも注目してください。

ここまできてようやく、publish_post のアクションフックに登録している関数名が prepare(準備)になっていた謎が解明されたのでした。 🙂

まとめ

以上で、お天気 API 取得処理の非同期化ができました。

今回のポイントは、

  • 外部 API 呼び出しなど外的要因で速度の低下が見込まれる場合は非同期化しよう。
  • WP-Cron にスケジュールされた処理は、サイトの表示処理とは別な WordPress が起動して行われるので非同期化される。
  • wp_schedule_single_event は WP-Cron にスケジュールを行う関数で、実行時刻の登録とアクションフック名と、アクションフックへの引数を登録することが出来る

でした。

というわけで、また次回。 🙂


目次

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

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

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() テンプレートタグ。

欧米か!

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

ということで 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 を用いたスケジュール化手法を解説しています。
テンプレートファイルからの情報出力
テンプレートファイルに処理を記述し出力する場合に、拡張モジュールと疎結合にする実装法を紹介します。アクションフックの活用例としても有効です。
管理画面をつける
管理画面に設定欄を設けユーザが拡張の機能をコントロールできるようにする手法の解説です。また、本連載中のソースコードに最終的なリファクタリングをかけ、他の開発者にこの拡張の意味を表現するプログラミングテクニックも解説します。
まとめ
本連載の概要とまとめ、そして目次です。

ダウンロードしたツイート CSV を Groovy + OrientDB で抽出

ワンショットではあるものの大きめのデータを処理する場合、データベースに入れたらいいなぁとおもいつつも、データベースサーバ起動するのが面倒だったり create table するのが面倒だったりすることがよくあります。

そんな時は組み込み可能な DB ってことで、こんなことに使ったら怒られそうではありますが OrientDB と Groovy の組み合わせ。タイムリーにも Twitter の自分の全ツイートが取得可能となったということで抽出をかけてみました。 🙂

OrientDB Graph-Document NoSQL dbms

OrientDB is an Open Source NoSQL DBMS with both the features of Document and Graph DBMSs. It’s written in Java and it’s amazing fast: can store up to 150,000 records per second on common hardware.

#もちろん OrientDB はサーバモードもありますです。

Twitter の全ツイートダウンロードですが、「ユーザ情報」の下の方にあるリクエストボタンをクリックするとメールリンクで CSV が送られてくるようです。

130326-0003

とりあえず Eclipse にファイルやライブラリを配置して・・・。(./file/Tweets は OrientDB によってつくられた DB の物理ファイルです)

130326-0001

まずは CSV2OrientDB のローダをかきました。 TweetsLoader.groovy

import au.com.bytecode.opencsv.CSVReader
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx
import com.orientechnologies.orient.core.record.impl.ODocument

// define CSV header (exclude expanded_urls)
def header = [
    "tweet_id"
    ,"in_reply_to_status_id"
    ,"in_reply_to_user_id"
    ,"retweeted_status_id"
    ,"retweeted_status_user_id"
    ,"timestamp"
    ,"source"
    ,"text"]

// load CSV
CSVReader reader = new CSVReader(new FileReader("./file/tweets.csv"))
List myEntries = reader.readAll()
reader.close()

// create OrientDB
ODatabaseDocumentTx db =
    new ODatabaseDocumentTx("local:./file/Tweets").create()

// import CSV
myEntries.each {
    ODocument doc = new ODocument("Tweet");
    def index = 0
    header.each { name ->
        doc.field(name, it[index])
        index++
    }
    doc.save()
}

// close OrientDB
db.close();

これを実行すると .csv が “local:./file/Tweets” DB に読み込まれます。スキーマレスなので適当にロードできます。お手軽。 🙂

でもって、できたデータベースにクエリーを発行します。 Groovy が入っているつぶやきを単純に like にて。 TweetsQuery.groogy

import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx
import com.orientechnologies.orient.core.record.impl.ODocument
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery

// open OrientDB
ODatabaseDocumentTx db =
    new ODatabaseDocumentTx(
        "local:./file/Tweets").open("admin", "admin");

// query
List<ODocument> result = db.query(
    new OSQLSynchQuery<ODocument>(
        "select * from Tweet where text like '%Groovy%'"));

// output
result.each {
    println it.field("text")
}

// close OrientDB
db.close();

手抜きコード申し訳ない(笑)

という感じで実行結果です。 あとは煮るなり焼くなりできますね。

130326-0002

なんだか懐かしいツイートがいっぱいでてきました。。

ちなみに5万弱のレコード数でしたが、それぞれの処理は待つ暇なく終わっています。

ワンショットとはいえ、普通のサーバ型の RDBMS を使ってしまうとプロジェクトだけでアプリを管理できずポータブル性がなくなってしまいますので組み込みDBの形式は便利ですね。サーバの起動にやきもきすることもありません。

また、OrientDB はドキュメント型ということで、スキーマレスで扱えるのもこういった用途に気軽で良いです。 このソースでは手抜きしていますが、POJO へのマッピングもできますのできちんとかくこともできます。

なんだかものすごいプロダクトをこんなことに使っていいんだろうか、、とも思ってしまいますが、、使い方のひとつということでお許しを。。 🙂