2012年01月10日に投稿

[WordPress]プラグインとして追加できるフィルター機能はどのように実現されているのか

add_filter関数はオリジナルのフィルター関数を登録するための関数で、フィルター関数を登録するとWordPressが出力するあらゆるデータを加工して出力を行うことができます。apply_filters関数は登録されたフィルター関数を実行するための関数です。どのような仕組みで実現されているのか気になったのでソースコードを確認しました。完全にトレースできたわけではありませんが、メモを兼ねて記事として公開します。バージョン3.1.3のソースコードとなります。

例えば下記のようなコードをプラグインファイルに記述してpluginディレクトリにアップロードすると、全ての記事タイトルに(こんにちは)という文字列が連結されて表示されます。これはadd_filter関数によって登録されたフィルター関数がapply_filters関数によって、画面に出力される前に(こんにちは)という文字列を連結させる処理を実行しているためです。

[php]
function addHello($title){
return $title . ‘(こんにちは)’;
}
add_filter(‘the_title’, ‘addHello’);
[/php]

add_filter関数は wp-include/plugin.php で下記のように定義されています。

[php]
function add_filter($tag, $function_to_add, $priority = 10, $accepted_args = 1) {
global $wp_filter, $merged_filters;

// 追加するフィルター関数にidをユニークな振ります。
$idx = _wp_filter_build_unique_id($tag, $function_to_add, $priority);

// フィルターをかけるタグ、優先度、idで構成された連想配列にフィルター関数名とそのフィルター関数が受け取る引数の数を書き込みます。
$wp_filter[$tag][$priority][$idx] = array(‘function’ => $function_to_add, ‘accepted_args’ => $accepted_args);

// 調査中(優先度で並び替えるためのフラグかな?)
unset( $merged_filters[ $tag ] );
return true;
}
[/php]

続いてapply_filters関数です。apply_filters関数は wp-include/plugin.php で下記のように定義されています。

[php]
function apply_filters($tag, $value) {
global $wp_filter, $merged_filters, $wp_current_filter;

$args = array();
$wp_current_filter[] = $tag;

// フィルタータグ all に対してフィルター関数が登録されていたら実行します。
// Do ‘all’ actions first
if ( isset($wp_filter[‘all’]) ) {
$args = func_get_args();
_wp_call_all_hook($args);
}

// フィルターをかけようとしているフィルタータグに対してフィルター関数が登録されていなければ出力をそのまま返して処理を終了します。
if ( !isset($wp_filter[$tag]) ) {
array_pop($wp_current_filter);
return $value;
}

// 優先度順にフィルター関数を並べ替えます。
// Sort
if ( !isset( $merged_filters[ $tag ] ) ) {
ksort($wp_filter[$tag]);
$merged_filters[ $tag ] = true;
}

// 配列のポインタを元に戻します。
reset( $wp_filter[ $tag ] );

// $argsにまだ引数を書き込んでいなければここで引数を全て取得します。
if ( empty($args) )
$args = func_get_args();

do {
// 今、実行しているフィルタータグに登録されているフィルター関数を順番に実行します。
foreach( (array) current($wp_filter[$tag]) as $the_ )
if ( !is_null($the_[‘function’]) ){
$args[1] = $value;
$value = call_user_func_array($the_[‘function’], array_slice($args, 1, (int) $the_[‘accepted_args’]));
}

// next関数によって配列のポインタを1つ先に進めます。次の優先度の配列が参照されます。
} while ( next($wp_filter[$tag]) !== false );

// 調査中
array_pop( $wp_current_filter );

// フィルター適用後の結果を返します。
return $value;
}
[/php]

36行目の補足
add_filter関数によって書き込んだのは $wp_filter[$tag][$priority][$idx] という配列であり、[‘function’]というラベルと[‘accepted_args’]というラベルを持っています。current($wp_filter[$tag]) とforeach構文により現在の$priority配列を$the_として処理を行います。

39行目の補足
apply_filters関数に渡された引数を、accepted_argsで指定した数だけフィルター関数に渡して実行します。apply_filters関数の第1引数はフィルタータグなので、第2引数以降がフィルター関数に渡される引数になります。そのためarray_slice関数で0番目の要素は対象になっていません。

$wp_current_filterと$merged_filtersについては調査中としました。これらの処理の中だけではあまり意味を持っていないようなので、他の処理との兼ね合いを見る必要がありそうです。僕のレベルでWordPressのソースを読むと非常に骨が折れますが勉強になる箇所が多いです…引き続きWordPressのソースコード解析を続けたいと思います。

関連記事

Leave a Reply