WordPress:アクションまたはフィルターに登録されている関数をリストアップする

WordPressでテーマやプラグインを作成していると、既定の動作に手を入れたくなることがよくある。
そのためにアクションやフィルターフックという仕組みがある。
しかし一つのフックが非常に多くの関数を呼び出しているため、適切な優先順位で呼び出すように関数を登録する必要がある。

そこでフックに登録済みの関数を優先順位と共にリストアップしてみる。

フック関数のリストアップコード

初稿では解説から始めたのですが、ソースコードの位置が把握しにくかったので、コードから掲載します。

次のコードはメインとなるコードです。

  • // フックに登録されている関数の一覧を取得
  • function list_filter( $hook_name ) {
  • global $wp_filter;
  • echo "*************".$hook_name."</br>";
  • if ( ! isset( $wp_filter[ $hook_name ] ) ) {
  • echo "No data";return;
  • }
  • $priorities = $wp_filter[ $hook_name ]->callbacks;
  • foreach ( $priorities as $priority => $callbacks ) {
  • foreach ( $callbacks as $value ) {
  • echo $priority . ' , ' . getFunctionInfo( $value['function'] ) . '</br>';
  • }
  • }
  • }
  • // フックから取得した関数情報からデータを取得
  • // $functionData: 関数名または[ クラス , 関数名 ]
  • function getFunctionInfo( $functionData ){
  • try{
  • $is_class = is_array( $functionData );
  • $rf = $is_class
  • ? new ReflectionClass($functionData[0])
  • : new ReflectionFunction($functionData);
  • $function_name = $is_class
  • ? $rf->getName() . '-&gt;'. $functionData[1]
  • : ($rf->getClosureScopeClass() === null ? ''
  • : $rf->getClosureScopeClass()->getName() . '-&gt;') . $rf->getName();
  • $start_line = $is_class
  • ? $rf->getMethod($functionData[1])->getStartLine()
  • : $rf->getStartLine() ;
  • $fnames = array_reverse( preg_split( "/[\\/\\\\]/" , $rf->getFileName()));
  • $fname = '/' . $fnames[1] . "/" . $fnames[0];
  • return $function_name . ' [' . $fname .'(' . $start_line . ')]';
  • }catch (Exception $e){
  • return '例外エラー: '. $e->getMessage();
  • }
  • }
AFFS Simple Code Viewer
Copy

関数が二つありますが、list_filter()関数がメインとなります。
関数を呼び出した後にフックが登録される可能性があるので、フッターなどで呼び出します。

しかし、運営中のブログはテンプレートを変更できないと思います。
その場合は記事中からショートコードで、wp_footerアクションをフックします。

  • $list_filter_targets = [];
  • function list_filter_shortcode($atts){
  • extract(shortcode_atts(array(
  • 'hook_name'=>"",
  • ),$atts));
  • global $list_filter_targets;
  • $list_filter_targets[] = $hook_name;
  • if( count($list_filter_targets ) === 1 ) {
  • add_action('wp_footer','list_filter_loop',9999999);
  • }
  • }
  • function list_filter_loop(){
  • global $list_filter_targets;
  • foreach ( $list_filter_targets as $target){
  • list_filter( $target );
  • }
  • }
  • add_shortcode('list_filter', 'list_filter_shortcode');
AFFS Simple Code Viewer
Copy

wp_headアクション、the_contentフィルター、wp_footerアクションを確認してみます。

次のように、記事中からショートコードを実行します。

  • [list_filter hook_name="wp_head"]
  • [list_filter hook_name="the_content"]
  • [list_filter hook_name="wp_footer"]
AFFS Simple Code Viewer
Copy

wp_headの結果は次のようになりました。

  • *************wp_head
  • 1 , _wp_render_title_tag [/wp-includes/general-template.php(1287)]
  • 1 , wp_enqueue_scripts [/wp-includes/script-loader.php(2099)]
  • 1 , wp_robots [/wp-includes/robots-template.php(20)]
  • 1 , wp_post_preview_js [/wp-includes/functions.php(7328)]
  • 1 , wp_maybe_inline_styles [/wp-includes/script-loader.php(2680)]
  • 2 , wp_resource_hints [/wp-includes/general-template.php(3302)]
  • 2 , feed_links [/wp-includes/general-template.php(3084)]
  • 3 , feed_links_extra [/wp-includes/general-template.php(3130)]
  • 7 , print_emoji_detection_script [/wp-includes/formatting.php(5716)]
  • 8 , wp_print_styles [/wp-includes/functions.wp-styles.php(44)]
  • 9 , wp_print_head_scripts [/wp-includes/script-loader.php(2052)]
  • 10 , rest_output_link_wp_head [/wp-includes/rest-api.php(956)]
  • 10 , rsd_link [/wp-includes/general-template.php(3212)]
  • 10 , wlwmanifest_link [/wp-includes/general-template.php(3222)]
  • 10 , locale_stylesheet [/wp-includes/theme.php(710)]
  • 10 , wp_generator [/wp-includes/general-template.php(4668)]
  • 10 , rel_canonical [/wp-includes/link-template.php(3960)]
  • 10 , wp_shortlink_wp_head [/wp-includes/link-template.php(4062)]
  • 10 , _custom_logo_header_styles [/wp-includes/theme.php(2858)]
  • 10 , wp_oembed_add_discovery_links [/wp-includes/embed.php(335)]
  • 10 , wp_oembed_add_host_js [/wp-includes/embed.php(375)]
  • 10 , wp_admin_bar_header [/wp-includes/admin-bar.php(1185)]
  • 10 , _admin_bar_bump_cb [/wp-includes/admin-bar.php(1197)]
  • 99 , wp_site_icon [/wp-includes/general-template.php(3249)]
  • 101 , wp_custom_css_cb [/wp-includes/theme.php(1858)]
AFFS Simple Code Viewer
Copy

the_contentの結果は次のようになりました。

  • *************the_content
  • 8 , WP_Embed->run_shortcode [/wp-includes/class-wp-embed.php(61)]
  • 8 , WP_Embed->autoembed [/wp-includes/class-wp-embed.php(439)]
  • 9 , do_blocks [/wp-includes/blocks.php(916)]
  • 10 , wptexturize [/wp-includes/formatting.php(37)]
  • 10 , wpautop [/wp-includes/formatting.php(442)]
  • 10 , shortcode_unautop [/wp-includes/formatting.php(820)]
  • 10 , prepend_attachment [/wp-includes/post-template.php(1668)]
  • 10 , wp_filter_content_tags [/wp-includes/media.php(1772)]
  • 10 , wp_replace_insecure_home_url [/wp-includes/https-migration.php(51)]
  • 11 , capital_P_dangit [/wp-includes/formatting.php(5515)]
  • 11 , do_shortcode [/wp-includes/shortcodes.php(206)]
  • 20 , convert_smilies [/wp-includes/formatting.php(3397)]
AFFS Simple Code Viewer
Copy

wp_footerの結果は次のようになりました。

  • *************wp_footer
  • 1 , wp_enqueue_global_styles [/wp-includes/script-loader.php(2309)]
  • 1 , wp_maybe_inline_styles [/wp-includes/script-loader.php(2680)]
  • 10 , the_block_template_skip_link [/wp-includes/theme-templates.php(111)]
  • 20 , wp_print_footer_scripts [/wp-includes/script-loader.php(2082)]
  • 1000 , wp_admin_bar_render [/wp-includes/admin-bar.php(74)]
AFFS Simple Code Viewer
Copy

解説

上のコードについて、解説してみます。

add_filter関数とadd_action関数について

WordPressでフィルターを追加するときはadd_filter関数を、アクションを追加するときはadd_action関数を呼び出します。
この関数は、次のように定義されています。

add_filter関数およびadd_action関数の定義

function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 )
function add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 )

$hook_name : フック名
$callback : 呼び出される関数
$priority : 優先度。規定値10
$accepted_args : 呼び出される関数の引数の数。規定値1

ただし引数$callbackはテキストまたは配列のどちらかを指定する。

  • 関数のときは関数名(文字列)
  • クラスのときは配列[クラス(インスタンスまたは文字列),関数名(文字列)]

問題なのがadd_action関数のコードです。次のようになっています。

  • function add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
  • return add_filter( $hook_name, $callback, $priority, $accepted_args );
  • }
AFFS Simple Code Viewer
Copy

ようするに、add_action関数はadd_filter関数をそのまま呼び出しているだけです。
フラグなども使用されていないことから、データ上はアクションとフィルターは同じものだと言えます。

次にadd_filter関数のコードを追っていくと、フックは$wp_filterという変数で管理されているのがわかります。

  • function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
  • global $wp_filter;
  • if ( ! isset( $wp_filter[ $hook_name ] ) ) {
  • $wp_filter[ $hook_name ] = new WP_Hook();
  • }
  • $wp_filter[ $hook_name ]->add_filter( $hook_name, $callback, $priority, $accepted_args );
  • return true;
  • }
AFFS Simple Code Viewer
Copy

フック名一覧の取得

$wp_filterはadd_filter関数の引数$hook_nameをキーとしてもつ連想配列です。
つまり、次のような形式になっています。

$wp_filter[$hook_name]

そのためキーを列挙すると、フック名の一覧を取得できます。

  • // フック名一覧の取得
  • function list_hook_name(){
  • global $wp_filter;
  • $hook_names = array_keys($wp_filter);
  • foreach ($hook_names as $name){
  • echo ($count++).$name.'<br>';
  • }
  • }
  • list_hook_name();
AFFS Simple Code Viewer
Copy

実行すると、非常に多くのフックが登録されているのがわかります。

  • 1:query
  • 2:pre_term_name
  • 3:pre_comment_author_name
  • 4:pre_link_name
  • 5:pre_link_target
  • ・・・
  • 345:is_wide_widget_in_customizer
  • 346:admin_print_scripts-widgets.php
  • 347:display_media_states
  • 348:admin_head-widgets.php
AFFS Simple Code Viewer
Copy

WordPressが用意しているものだけで350近くありました。

フック情報の取得

$wp_filter[$hook_name]は、値としてwp-includes/class-wp-hook.phpで定義されているWP_Hookクラスのインスタンスを持っています。

$wp_filter[$hook_name] → WP_Hookクラスのインスタンス

WP_Hookクラスはpublicな$callbacksプロパティを持っています。
$callbacksプロパティは引数$priorityと引数$callbackをキーとする2次元配列です。

ただし$callbackが文字列でない場合、次の条件で文字列に変換されます。

$callbackの文字列化
  • 文字列 → 文字列を使用
  • [ 文字列 , 文字列 ] → 文字列[0] . '::' . 文字列[1]を使用
  • [ オブジェクト , 文字列 ]→ spl_object_hash(オブジェクト) . 文字列を使用

つまり次のような関係となっています。

$wp_filter[$hook_name]->callbacks[ $priority ][ $callback(文字列化) ]

そして$callbacks[ $priority ][ $callback(文字列化) ]の値は、'function'と'accepted_args'をキーとする配列です。
'function'には引数として受け取った$callbackが、'accepted_args'には$accepted_argsがセットされます。

$callbacks[ $priority ][ $callback(文字列化) ]
→ [ 'function' => $callback , 'accepted_args' => $accepted_args]

ここまでわかれば、後は順番に取り出していくだけです。

  • // フックに登録されている関数の一覧を取得
  • function list_filter( $hook_name ) {
  • global $wp_filter;
  • echo "*************".$hook_name."</br>";
  • if ( ! isset( $wp_filter[ $hook_name ] ) ) {
  • echo "No data";return;
  • }
  • $priorities = $wp_filter[ $hook_name ]->callbacks;
  • foreach ( $priorities as $priority => $callbacks ) {
  • foreach ( $callbacks as $value ) {
  • echo $priority . ' , ' . getFunctionInfo( $value['function'] ) . '</br>';
  • }
  • }
  • }
  • // フックから取得した関数情報からデータを取得
  • // $functionData: 関数名または[ クラス , 関数名 ]
  • function getFunctionInfo( $functionData ){
  • try{
  • $is_class = is_array( $functionData );
  • $rf = $is_class
  • ? new ReflectionClass($functionData[0])
  • : new ReflectionFunction($functionData);
  • $function_name = $is_class
  • ? $rf->getName() . '-&gt;'. $functionData[1]
  • : ($rf->getClosureScopeClass() === null ? ''
  • : $rf->getClosureScopeClass()->getName() . '-&gt;') . $rf->getName();
  • $start_line = $is_class
  • ? $rf->getMethod($functionData[1])->getStartLine()
  • : $rf->getStartLine() ;
  • $fnames = array_reverse( preg_split( "/[\\/\\\\]/" , $rf->getFileName()));
  • $fname = '/' . $fnames[1] . "/" . $fnames[0];
  • return $function_name . ' [' . $fname .'(' . $start_line . ')]';
  • }catch (Exception $e){
  • return '例外エラー: '. $e->getMessage();
  • }
  • }
AFFS Simple Code Viewer
Copy

実行すると 優先順位,関数名[定義しているファイル名(開始行)] の形式でリストアップされます。

ここでは二つの関数を作成しました。
list_filter関数とgetFunctionInfo関数です。

■list_filter関数

list_filter関数は、$wp_filterからフックに登録されている関数を抽出しています。

■getFunctionInfo関数

getFunctionInfo関数は、関数が定義されているソースファイル名などの情報を取得しています。
単体で使用することで、フックに関連していない関数の情報も取得できます。

この関数は別のサイトで作成したコードをWordPressに対応させたものです。
詳しくは次を見てください。
【PHP】 関数・クラスが定義されているソースファイル名を取得する