WordPressプラグインの仕組みとクラスを利用したより実践的な解説


投稿日:2015年9月18日
  • 7
  • 0
  • 49



WordPressプラグインの作り方、実践編

wordpress-plugin_02

WordPressプラグインの解説」第2弾。

このページではテンプレートタグフックを利用し、細かな仕様や、動作の制御方法も紹介します。
実用的なプラグインを開発するには必要になる、アクティベート・ディアクティブモード処理。
さらに、より実践的な例としてクラス化についても解説します。


このページの目次


プラグインでテンプレートタグを定義して利用する

まずは単純な例として独自のテンプレートタグを定義します。
テンプレートタグと言ってもただの関数です。WordPressではプラグインで定義した関数もグローバルとして扱われるため一意(ユニーク)である必要があります。WordPressの内部関数や他のプラグインと被らないように配慮してください。とりあえず今回は解説用に「oxy_」という接頭辞を付けます。

前回の投稿で作成したhello-world.phpに追加します。

function oxy_hello_world() {
	return "Hello World";
}

ではこのテンプレートタグを投稿のテンプレートファイルに記述して表示されるかテストします。(対応するファイルはテーマごとに異なりますがloop-single.phpなど)

echo oxy_hello_world();
wordpress-plugin02

無事に「Hello World」が表示されていますね。

ただの関数なので引数も渡せます。

hello-world.phpを変更します。

function oxy_hello_world( $name ) {
	return "Hello " . $name;
}

テンプレート側も変更します。

echo oxy_hello_world( "Oxy" );
wordpress-plugin03

無事に引数も渡せました。
このようにユーザーにタグを記述してもらうことで動作するのが、テンプレートタグを使ったプラグインです。


プラグインでアクションフックを利用する

一連の解説でも触れていますが、フックにはアクションフックフィルターフックがあります。

動作のわかりやすいものということで、今回は「admin_menu」というアクションフックを利用します。
admin_menu」は管理画面が読み込まれた時点で実行されるフックです。

英語版Codexのadmin_menuの解説

hello-world.phpに記述します。

function oxy_hello_world() {
	add_menu_page( 'hoge','hoge','manage_options','myplugin_setting' );
}
add_action( 'admin_menu', 'oxy_hello_world' );

保存したら管理画面を開いてください。すると「hoge」というメニューが表示されていると思います。

wordpress-plugin04

add_action()の第1引数にアクションフックのadmin_menuを指定しています。第2引数で自分で作成した関数oxy_hello_worldを指定しています。
add_menu_page()はメニューを追加するタグで、ここでは詳細は解説しませんが、この記述でhogeを表示しています。

add_action()を書く場所ですが、指定する関数の前に置いたり後に置いたりadd_action()だけでまとめたりと、プラグイン作者の好みでバラバラです。特に決まりは無いですが、バラバラだと迷いやすいので、プラグイン内でルールを決めて書くようにしましょう。

フックの実行順序と、デバックする際のコツ

アクションフックは特定のアクションが起こった時点で実行されますが、実行結果の確認などがしにくいものもあります。
そんなときは定義した関数内でメッセージと共に処理を停止するdie()を使用すると把握が楽になります。

例えばどのアクションが、どの順番で実行されているか把握する場合には以下のようにします。

function oxy_hello_world1() {
	die("1番目に停止したよ");
}
add_action('admin_menu', 'oxy_hello_world1');

function oxy_hello_world2() {
	die("2番目に停止したよ");
}
add_action('admin_menu', 'oxy_hello_world2');

このように書いて設定画面を開くと、「1番目に停止したよ」が表示されます。2つとも同じアクションに対してフックしているので、先に登録されているものが実行されます。

このような場合に「2番目に停止したよ」を実行したい場合は優先順位を指定します。
優先順位は第3引数で指定します。デフォルトが「10」で数字が小さいほど先に実行されます。今回は「oxy_hello_world2」を先に実行したいので「9」にしました。

function oxy_hello_world1() {
	die("1番目に停止したよ");
}
add_action('admin_menu', 'oxy_hello_world1', 10);

function oxy_hello_world2() {
	die("2番目に停止したよ");
}
add_action('admin_menu', 'oxy_hello_world2', 9);

再度実行すると今度は「2番目に停止したよ」が表示されたと思います。

また、add_action()には第4引数まであり、関数へ送る引数の数を指定することができます。
扱うことのできる引数の数や、内容はアクションフックによって異なるので、詳しくはCodexの解説をご覧ください。

アクションフックsave_postを使ったサンプル

もうひとつ実践的な例としてsave_postというアクションフックを使ってみます。これは投稿を保存した時に実行されるアクションフックです。

Codexのsave_postの解説

Codexではメールで通知するサンプルが掲載されていますが、ローカル環境だとメールの送信設定がされていないことも多いと思うので、投稿の内容を表示してみます。
投稿のループ内ではないのでget_the_content()では本文は取得できません。IDとフィールドのKeyを指定して本文を取得しています。

function oxy_hello_world( $post_id ) {

	if ( wp_is_post_revision( $post_id ) ){
		return;
	}

	$post_title = get_the_title( $post_id );
	$post_url = get_permalink( $post_id );
	$content = get_post_field( 'post_content', $post_id );

	die( $post_title . $post_url . $content );
}
add_action( 'save_post', 'oxy_hello_world' );

まずwp_is_post_revision()でリビジョン機能による自動保存だった場合に処理を停止しています。
これはsave_postというアクションフックは「投稿が保存された時点で実行」されるため、ユーザーが更新ボタンをクリックした場合も、自動でリビジョンが保存された場合も、同じように実行されてしまうからです。

では、投稿で新規追加を開き、タイトルと本文を入力して公開してみてください。
すると、「タイトル、URL、本文」の順番で表示されるはずです。
このようにアクションフックはフックの特性とWordPressの動作を把握して実装する必要があります。

では、bookというカスタム投稿タイプの場合、book_titleというカスタムフィールドにタイトルと同じ文字列を追加するという処理を追加します。

function oxy_hello_world( $post_id, $post, $update ) {

	if ( wp_is_post_revision( $post_id ) ) {
		return;
	}

	$slug = 'book';

	if ( $slug != $post->post_type ) {
		return;
	}

	$post_title = get_the_title( $post_id );

	update_post_meta($post_id, 'book_title', $post_title);
}
add_action( 'save_post', 'oxy_hello_world', 10, 3 );

add_action()の第4引数で、関数に渡す引数を3つに指定
受け取る側の関数であるoxy_hello_world()では、$postからpost_typeを取得して投稿タイプがbookかどうかを調べています。
後はupdate_post_meta()book_titleというKey$post_titleを代入しています。

応用すれば投稿メンバーによって異なる紹介文を自動で追加したり、特定のフィールドに同じ値を入力するといったルーティンワークを軽減できます。

投稿の内容を変更するサンプル

投稿の内容自体も変更できます。投稿の内容を変更するにはwp_update_post()でIDを指定して内容を変更します。

function oxy_hello_world( $post_id ) {

	if ( wp_is_post_revision( $post_id ) ) {
		return;
	}

	remove_action( 'save_post', 'oxy_hello_world' );

	$content = get_post_field('post_content', $post_id);

	wp_update_post( array( 'ID' => $post_id, 'post_content' => $content . '追加したよ' ) );

	add_action( 'save_post', 'oxy_hello_world' );

}
add_action( 'save_post', 'oxy_hello_world' );

Codexでも解説されていますが、1点だけ注意点がありwp_update_post()で投稿をアップロードし直す時点でもsave_postアクションフックが呼び出されるため、無限ループに陥ります。以下の例で言えば「追加したよ」が永遠に追加されてしまいます。

それを避けるためにremove_action()でアクションフックを削除した後にwp_update_post()を実行。削除しただけでは次回アクションフックが実行されないので、その後に再びadd_action()で同じアクションフックをセットし直してます。

これも応用すればテンプレートに手を加えずに新たな要素を自動で追加するといったプラグインを作成できます。

以上のようにアクションフックは順序を変更したり関数に引数を渡したり関数の内部で削除や追加することでプラグインを作成することができます。


プラグインでフィルターフックを利用する

アクションフックに続いて、フィルターフックについても見ていきます。
既に触れましたが、アクションフックがアクションに対してフックしていたのに対して、フィルターフックデータに対してフックします。

今回は編集画面で表示する投稿の内容をフックするcontent_edit_preを例に解説します。

英語版Codexのcontent_edit_preの解説

function oxy_hello_world( $content, $post_id ) {
	return $content . "フィルターフックで追加したよ";
}
add_filter( 'content_edit_pre', 'oxy_hello_world', 10, 2 );

フィルターフックの場合はadd_filter()と書きます。書き方はアクションフックと同じで第1引数にフック名第2引数に関数第3引数に実行順序第4引数に関数に渡す引数の数を指定します。

このフィルターフックをプラグインに書いて投稿の編集画面を表示してみてください。

wordpress-plugin05

本来「本文を入力するよ」しか入力していなかった本文に、「フィルターフックで追加したよ」が追加されています。
この状態で「投稿を表示する」で表示してみます。

wordpress-plugin06

すると本文には「本文を入力するよ」しか表示されていません。
これはcontent_edit_preによってデータベースから本文を取得して投稿画面に表示するまでの間に、フィルターフックを適用しているからです。この時点ではデータベースには「フィルターフックで追加したよ」というデータは追加されていません。

もちろん編集画面で「フィルターフックで追加したよ」と表示されている状態で「更新ボタン」をクリックすれば追加できます。

既に「フィルターフックで追加したよ」が入力されている場合は追加しないといった変更も以下のように簡単に実装できます。

function oxy_hello_world( $content, $post_id ) {
	if ( strpos($content, "フィルターフックで追加したよ") === FALSE ) {
		return $content . "フィルターフックで追加したよ";
	} else {
		return $content;
	}
}
add_filter( 'content_edit_pre', 'oxy_hello_world', 10, 2 );

毎回入力する定型文がある場合なんかに利用すると便利です。ユーザーを判定する関数と組み合わせれば、編集者のごとに定型文を追加するなんてことも簡単に実装できます。

タイトルを出力するまでの間にフィルターフックを利用する

続いてthe_titleで、データベースからデータを取得して、投稿に表示するまでの間にフィルターしてみます。

function oxy_hello_world( $title ) {
    return mb_strimwidth( $title, 0, 10, "…", "UTF-8" );
}
add_filter( 'the_title', 'oxy_hello_world' );

mb_strimwidth()で文字数を(全角は2文字換算で)10文字以下に丸めてます。投稿を表示すると以下のようになります。

wordpress-plugin07

データベースに保存されたデータ自体は変更せず、投稿を表示するまでの間にフィルターフックを適用しています。

以上、まとめるとWordPressのプラグインには3つの基本的な動作方法がありました。

  • 1.ユーザーにテンプレートタグを追加してもらって動作させるもの。
  • 2.アクションフックで特定のアクションをきっかけに動作するもの。
  • 3.フィルターフックで特定のデータを書き換えることで動作するもの。

自分のプラグインがどのタイプかを把握して、適切な実装方法を選択してください。


プラグインをクラス化する

プラグインで使う関数はグローバルで扱われるため、全て一意である必要があると説明しました。
対策として接頭辞を付けると紹介しましたが、プラグインの構造が複雑になると全ての関数名で一意性を保つというのは無理があります。そこでクラスを利用してカプセル化します。

この解説ではPHPのクラスに関する基本的な知識がある前提で進めます

クラスを利用したコードは慣れないと敷居が高いですが、実用的なプラグインを作る上でどうしても必要な知識になります。ぜひ、この機会に学んでみてください。
以下、クラスを理解する上でお勧めのサイトです。上から順番に読んでいくと理解しやすいと思います。

PHPオブジェクト指向入門
PHPの静的変数 (static変数) の挙動まとめ
PHPを愛する試み 〜self:: parent:: static:: および遅延静的束縛〜

例えば以下の様な被りそうなhogeという定数も、クラス内で定義するぶんには問題になりません。

class Hello_World {
	const hoge = "Hello World!";
}
echo Hello_World::hoge; // Hello World!

プラグインで関数を使うのにわざわざインスタンスを作成するのも面倒なので、普通は呼び出すテンプレートタグだけクラス外に記述します。

class Hello_World {
	public function hoge() {
		echo "Hello World!";
	}
}

function oxy_hello_world() {
	$HelloClass = new Hello_World();
	return $HelloClass->hoge(); // Hello World!
}

このように書いておけばテンプレートにはoxy_hello_world()と記述するだけで「Hello World!」が表示されます。

注意点としてフックで関数を指定する場合は、クラスを指定する必要があります。
例えば上で解説したフィルターフックを使う場合は以下のように第2引数に配列を指定して、クラス名関数名と指定する必要があります。

class Hello_World {

	public function __construct() {
		add_filter( 'the_title', array( $this, 'hello_world' ) ); // $this(疑似変数)と関数を指定
	}

	public function hello_world( $title ) {
	    return mb_strimwidth( $title, 0, 10, "…", "UTF-8" );
	}
}

$HelloClass = new Hello_World();

このようにプラグインを読み込んだ時点で有効にするフックコンストラクタで指定するのが一般的です。

クラス外でフックしたい場合は以下のようにインスタンスを作成して指定します。

class Hello_World {
	public function hello_world( $title ) {
	    return mb_strimwidth( $title, 0, 10, "…", "UTF-8" );
	}
}

$HelloClass = new Hello_World();
add_filter( 'the_title', array( $HelloClass, 'hello_world' ) );

また、クラスを定義する際は同一のクラスが既に定義されていないか調べるためにclass_exists()で調べるのがセオリーのようです。(プラグインの重複対策)

if ( !class_exists('Hello_World') ) {
	class Hello_World {
		public function hello_world( $title ) {
		    return mb_strimwidth( $title, 0, 10, "…", "UTF-8" );
		}
	}
}

このようにクラスを利用すれば、オブジェクト化によるメンテナンス性の向上はもちろん、カプセル化することで一意な名前を考える煩わしさからは開放されます。
よほど単純な構造のプラグインでない限りクラス化することをお勧めします。


クラスの分離・外部データの取り込み

多機能になると多数のクラスを作成して、ファイルを分離することもあると思います。
ファイルの読み込み方は様々ありますが、require_once()を利用して、dirname(__FILE__)でメインファイルのディレクトリを取得して、ファイル名を指定するのが間違いが無いと思います。

プラグインディレクトリにlibディレクトリを作成し、hoge.phpを読み込んでみます。

hello-world.php

require_once dirname(__FILE__) . '/lib/hoge.php';

$HelloClass = new Hello_World();
echo $HelloClass->hello_world(); // Hello_World!

/lib/hoge.php

if ( !class_exists('Hello_World') ) {
	class Hello_World {
		public function hello_world() {
		    return "Hello_World!";
		}
	}
}

これでプラグインを読みこめばHello_World!と表示されます。
この辺は普通にPHPで開発するのと同じですね。

どの時点でクラス化して、どの時点で別ファイルにするかは、自分でわかりやすいように作るのが一番だと思います。


アクティベート、ディアクティベート処理

プラグインを有効にした時だけ実行したい処理はregister_activation_hook()
プラグインを無効にした時だけ実行される処理はregister_deactivation_hook()を使います。

例えばプラグインで使うデフォルトの設定を保存したり、テーブルを作成したり、といった用途に使用します。
利用者のデータベースにゴミを残さないためにも、プラグインを停止時に作成した設定やテーブルを削除することを忘れないようにしてください。

アクティベートについてのCodexの解説

解説にもありますが、第1引数にはファイルのフルパス、第2引数には実行する関数を指定します。

具体的には以下のようにして使用します。

register_activation_hook( __FILE__, array( 'Hello_World', 'activate' ) );
register_deactivation_hook( __FILE__, array( 'Hello_World', 'deactivate' ) );

class Hello_World {
	public function activate() {
		self::add_user_setting();
	}

	public function activate() {
		self::remove_user_setting();
	}

	private static function add_user_setting() {
		$oxy_user_setting = get_site_option('oxy_user_setting');
		if ( ! $oxy_user_setting ) {

			// 設定のデフォルトの値
			$user_setting = array(
				'twitter' => 1,
				'facebook' => 1,
				'google' => 1,
				'hatena' => 1,
				'pocket' => 1,
				'feedly' => 1,
				'rss_type' => ''
			);
			update_option( 'oxy_user_setting', $user_setting );
		}
	}

	private static function remove_user_setting() {
		delete_option('oxy_user_setting');
	}
}

アクティベート・ディアクティベート処理での注意点

Hello_Worldクラス内のactivate()add_user_setting()という関数を呼び出しています。転送コールself::で指定しており、add_user_setting()staticで静的に呼ばれていることに注意してください。

これはCodexの解説にもありますが、register_activation_hook()activate_pluginという特殊な関数で呼ばれており、この時点ではHello_Worldクラスはコールされておらず、疑似変数$thisを使ったり、クラス内のグローバル変数を利用できないからです。

普通に$thisを使おうとすると「Fatal error: Using $this when not in object context in…」というエラーで実行できません。(というよりCodexの解説を流し読みした程度だとまず間違いなくハマるポイントだと思います)

register_activation_hook()について

add_user_setting()ではupdate_option()でプラグインのデフォルトの設定を<接頭辞> + optionsテーブルに保存しています。このようにすることでプラグインを有効にした時点でデフォルトの設定を保存しておくことができます。

ちなみに上記の設定だと、以下の様にシリアライズされて保存されます。

option_nameというカラムにoxy_user_setting
option_valueカラムa:7:{s:7:”twitter”;i:1;s:8:”facebook”;i:1;s:6:”google”;…(省略)}が保存されます。

このようにKeyとValueを連想配列で保存しておくと、1つのオプションに複数の設定を保存することができます。
予め連想配列で設計しておくとオプションが増えた場合にも対応が楽です。

また、オプションテーブルはWordPressとプラグインで共通なので、option_nameに登録するKey一意である必要があります。

register_deactivation_hook()について

register_deactivation_hook()ではdelete_option()で登録したオプションを削除しています。
このように1つのオプション名でまとめておけば、削除は一行で済みます。

アクティベート処理の実装方法(2つ目)

静的メソッドは扱いがややこしいので使いたくないという場合は、先にインスタンスを生成してHello_Worldクラスをコール後にactivate_plugin関数で実行する方法もあります。
以下の例は上と全く同じように動作します。

$Hello_World = Hello_World::get_instance();

register_activation_hook( __FILE__, array( $Hello_World, 'activate' ) );
register_deactivation_hook( __FILE__, array( $Hello_World, 'deactivate' ) );

class Hello_World {

	static $instance;

	public static function get_instance() {
		if( !isset( self::$instance ) ) {
			$class = __CLASS__;
			self::$instance = new $class();
		}

		return self::$instance;
	}

	public function activate() {
		$this->add_user_setting();
	}

	public function deactivate() {
		$this->remove_user_setting();
	}

	private function add_user_setting() {
		$oxy_user_setting = get_site_option('oxy_user_setting');
		if ( ! $oxy_user_setting ) {

			// 設定のデフォルトの値
			$user_setting = array(
				'twitter' => 1,
				'facebook' => 1,
				'google' => 1,
				'hatena' => 1,
				'pocket' => 1,
				'feedly' => 1,
				'rss_type' => ''
			);
			update_option( 'oxy_user_setting', $user_setting );
		}
	}

	private function remove_user_setting() {
		delete_option('oxy_user_setting');
	}
}

アクティベート処理の実装方法(3つ目)

このまとめを作成するにあたり再度調べ直すと、register_activation_hook()内でインスタンスを作成できることがわかりました。
以下のようにすれば静的にしたり、クラス内でインスタンスを作成する必要もありません。

私が利用している約20個のプラグインを調べましたが、この方法を採用しているものはありませんでした。しかし、この方法が一番お勧めです。
注意点があり、register_activation_hookでも__construct()が実行されるので、admin_menuなど複数回実行されてしまいます。そのような場合はプラグインをアクティベートした時と、ディアクティベートした時限定のクラスを用意してください。

register_activation_hook( __FILE__, array( new Hello_World, 'activate' ) );
register_deactivation_hook( __FILE__, array( new Hello_World, 'deactivate' ) );

class Hello_World {

	public function activate() {
		$this->add_user_setting();
	}

	public function deactivate() {
		$this->remove_user_setting();
	}

	private function add_user_setting() {
		$oxy_user_setting = get_site_option('oxy_user_setting');
		if ( ! $oxy_user_setting ) {

			// 設定のデフォルトの値
			$user_setting = array(
				'twitter' => 1,
				'facebook' => 1,
				'google' => 1,
				'hatena' => 1,
				'pocket' => 1,
				'feedly' => 1,
				'rss_type' => ''
			);
			update_option( 'oxy_user_setting', $user_setting );
		}
	}

	private function remove_user_setting() {
		delete_option('oxy_user_setting');
	}
}

どう考えてもこれが一番スマートですね。


クラス化や静的変数なんかも出てきてややこしくなってきましたが、1度仕組みを理解してしまえば理屈は簡単です。

次は「WordPressのプラグインでデータベースを作成・削除・アップデートする方法」です。



現在のページを共有する



現在のページに関連する記事


おすすめの記事


コメントを残す

コメントは認証制のため、すぐには反映されません。

プログラミングに関する質問は「日本語でプログラミングの悩みを解決するQ&Aサイト sukegra」をご利用ください。