フックをコアから理解 第13回 19年12月 / 最終更新:2019.12.23

こんにちはー、プライム・ストラテジーの森下です。

突然ですが、この度共著で『ビジネスサイトを作って学ぶ WordPressの教科書 Ver.5.x対応版』という書籍を出版しました!

簡単にこの本を説明すると本の手順通りに進めていくだけで完全オリジナルのビジネスサイトを構築することが出来ると同時にWordPressの導入、構築方法について優しくマスターすることが出来ます。
また、初心者がつまずきそうな箇所についてはYoutubeに説明動画をあげたり等しており、今後も読者をサポートし続けていきます!
詳細は下記のFacebookページで確認出来ます!
https://www.facebook.com/wordpress5book/

書店に限らずAmazonや楽天市場等でも購入出来ます!ということでちょっとした宣伝でした。

 

それでは気を取り直してコラムに移っていきたいと思います。

今回は何気なく使用しているWordPressのadd_filterやadd_action関数について詳しくコアファイル追って確認・解説していきます。

WordPressの前提知識

WordPressは前提として元々完成されたシステムであり、アップデートする度にコアファイルもアップデートされるためコアファイルを直接編集するのはタブーです。そのためWordPressにはフックというコアファイルを直接編集せずとも独自に記述した処理をコアファイル内に差し込める独自の機能が組み込まれています。

WordPressの構成要素について

WordPressは下図のように6つの要素から成り立っています。テーマやプラグインを作成して動作するのはWordPress全体の流れを制御しているコアファイルに読み込まれているためです。

インターネット上に散らばっているWordPressのカスタマイズレシピやプラグインなどだけで欲しい機能を実現しているだけではこの仕組み・構造を理解する機会がないかと思いますので、きちんと理解するようにしましょう。WordPressの本質である仕組み・構造を理解すればするほど問題解決力、実装能力は格段にアップします。

フックのサンプルコード

おそらくカスタマイズ経験がある方であれば下記のようなソースコードをfunctions.phpに記述したことがあるかと思います。

サンプル①

1
2
3
4
function custom_login_message( $message ) {
    return '○○へようこそ';
}
add_filter( 'login_message', 'custom_login_message' );

サンプル②

1
2
3
4
5
6
7
8
9
10
11
12
13
function custom_login_style() {
?>
 <style>
     body.login div#login h1 a {
     background-image: url("画像のURL");
     background-size: cover;
     height: 60px;
     width: 100%;
 }
 </style>
<?php
}
add_action( 'login_enqueue_scripts', 'custom_login_style' );

サンプル①はフィルターフック、サンプル②はアクションフックに独自の関数をフックしています。各関数を簡単に説明するとサンプル①のcustom_login_messageで管理画面に独自のメッセージを表示しており、サンプル②のcustom_login_styleで管理画面に独自のスタイルシートを適用しています。これらはググるとすぐに出てきますが、内部の流れを理解していますでしょうか。

コアファイルの処理の流れ

まずadd_actionが何をしているかgrepでコアファイルを検索します。ちなみに使用しているWordPressのバージョンは5.3になります。wp-includesディレクトリで下記のlinuxコマンドを実行してどこに定義されているか検索します。

1
grep -inr "function" | grep "add_action"

すると

1
2
3
4
5
rest-api.php:175:       add_action( 'deprecated_function_run', 'rest_handle_deprecated_function', 10, 3 );
plugin.php:403:function add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
plugin.php:785: add_action( 'activate_' . $file, $function );
plugin.php:808: add_action( 'deactivate_' . $file, $function );
rewrite.php:259:        add_action( $hook, $function, 10, 2 );

でplugin.phpの403行目に該当箇所があることが分かります。早速覗いてみましょう。

1
2
3
function add_action( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
    return add_filter( $tag, $function_to_add, $priority, $accepted_args );
}

まさかのadd_filter関数に引数を渡しているだけなのが確認できます。Codexにも記述されていますがadd_actionはadd_filterのエイリアスなのです。

続いてadd_filter関数の内部をみてみましょう。

1
2
3
4
5
6
7
8
function add_filter( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
    global $wp_filter;
    if ( ! isset( $wp_filter[ $tag ] ) ) {
        $wp_filter[ $tag ] = new WP_Hook();
    }
    $wp_filter[ $tag ]->add_filter( $tag, $function_to_add, $priority, $accepted_args );
    return true;
}

add_filterは何やら処理をやっているので、順を追ってみていきましょう。

1
if ( ! isset( $wp_filter[ $tag ] ) ) {

で既にフック名をキーとする配列が存在するか確認しています。存在しなければそのフック名をキーとしてWP_Hookのインスタンスをバリューとして格納します。そして$wp_filter[‘フック名’]->add_filter()でクラスのメソッドに処理を投げています。

次にWP_Hookというクラスがどこに定義されているか確認します。

1
grep -inr "class" | grep WP_Hook

すると

1
2
class-wp-hook.php:3: * Plugin API: WP_Hook class
class-wp-hook.php:18:final class WP_Hook implements Iterator, ArrayAccess {

でclass-wp-hook.phpの18行目に定義されていることが分かります。クラス内のadd_filterを確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function add_filter( $tag, $function_to_add, $priority, $accepted_args ) {
    //関数名があればidxに関数名が入り、無名関数であれば固有のIDが発行されidxに格納される
    $idx              = _wp_filter_build_unique_id_wp_filter_build_unique_id( $tag, $function_to_add, $priority );
    $priority_existed = isset( $this->callbacks[ $priority ] );
    //プロパティ$callbacks['優先順位']['固有ID']に引数で受け取った関数名と引数の数を連想配列として格納
    $this->callbacks[ $priority ][ $idx ] = array(
        'function'      => $function_to_add,
        'accepted_args' => $accepted_args,
    ); 
 
    //同じフックに関数が追加される度にここで優先度で並び替えをする
    if ( ! $priority_existed &amp;&amp; count( $this->callbacks ) > 1 ) {
        ksort( $this->callbacks, SORT_NUMERIC );
    }  
 
    if ( $this->nesting_level > 0 ) {
        $this->resort_active_iterations( $priority, $priority_existed );
    }  

ここでは$wp_filterに登録されている関数を登録するのが主な処理内容になります。その結果wp_filterを覗いてみると上部で登録した関数が登録されていることが確認できます。

サンプル①

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  ["login_message"]=>
  object(WP_Hook)#397 (5) {
    ["callbacks"]=>
    array(1) {
      [10]=>
      array(1) {
        ["custom_login_message"]=>
        array(2) {
          ["function"]=>
          string(20) "custom_login_message"
          ["accepted_args"]=>
          int(1)
        }
      }
    }
    ["iterations":"WP_Hook":private]=>
    array(0) {
    }
    ["current_priority":"WP_Hook":private]=>
    array(0) {
    }
    ["nesting_level":"WP_Hook":private]=>
    int(0)
    ["doing_action":"WP_Hook":private]=>
    bool(false)
  }
}

サンプル②

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  ["login_enqueue_scripts"]=>
  object(WP_Hook)#396 (5) {
    ["callbacks"]=>
    array(1) {
      [10]=>
      array(1) {
        ["custom_login_style"]=>
        array(2) {
          ["function"]=>
          string(18) "custom_login_style"
          ["accepted_args"]=>
          int(1)
        }
      }
    }
    ["iterations":"WP_Hook":private]=>
    array(0) {
    }
    ["current_priority":"WP_Hook":private]=>
    array(0) {
    }
    ["nesting_level":"WP_Hook":private]=>
    int(0)
    ["doing_action":"WP_Hook":private]=>
    bool(false)
  }
}

このような流れでwp-setting.phpの中でmu-plugins内のPHPファイル、pluginの中のPHPファイル、アクティブテーマのfunctions.php、wp-includes/default-filters.php等のコアがフックしているファイルを読み込み$wp_filterに関数を溜めていきます。

ここで1つ覚えていて欲しいのがテーマ内のfunctions.phpを読み込む前に発火するアクション・フィルターフックが存在するということです。たまにテーマのfunctions.phpに書いた関数が発火しない等の質問をみることがありますが、wp-setting.phpを見れば原因が一発で分かります。

そして$wp_filterに登録された関数を優先順位にしたがって実行するのがdo_actionとapply_filterです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function do_action( $tag, ...$arg ) {
    global $wp_filter, $wp_actions, $wp_current_filter;
     
    if ( ! isset( $wp_actions[ $tag ] ) ) {
        $wp_actions[ $tag ] = 1;
    } else {
        ++$wp_actions[ $tag ];
    }
 
    // Do 'all' actions first
    if ( isset( $wp_filter['all'] ) ) {
        $wp_current_filter[] = $tag;
        $all_args            = func_get_args();
        _wp_call_all_hook( $all_args );
    }
 
    if ( ! isset( $wp_filter[ $tag ] ) ) {
        if ( isset( $wp_filter['all'] ) ) {
            array_pop( $wp_current_filter );
        }
        return;
    }
 
    if ( ! isset( $wp_filter['all'] ) ) {
        $wp_current_filter[] = $tag;
    }
 
    if ( empty( $arg ) ) {
        $arg[] = '';
    } elseif ( is_array( $arg[0] ) &amp;&amp; 1 === count( $arg[0] ) &amp;&amp; isset( $arg[0][0] ) &amp;&amp; is_object( $arg[0][0] ) ) {
        // Backward compatibility for PHP4-style passing of `array( &amp;$this )` as action `$arg`.
        $arg[0] = $arg[0][0];
    }
 
    $wp_filter[ $tag ]->do_action( $arg );
 
    array_pop( $wp_current_filter );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
function apply_filters( $tag, $value ) {
    global $wp_filter, $wp_current_filter;
 
    $args = func_get_args();
 
    // Do 'all' actions first.
    if ( isset( $wp_filter['all'] ) ) {
        $wp_current_filter[] = $tag;
        _wp_call_all_hook( $args );
    }
 
    if ( ! isset( $wp_filter[ $tag ] ) ) {
        if ( isset( $wp_filter['all'] ) ) {
            array_pop( $wp_current_filter );
        }
        return $value;
    }
 
    if ( ! isset( $wp_filter['all'] ) ) {
        $wp_current_filter[] = $tag;
    }
 
    // Don't pass the tag name to WP_Hook.
    array_shift( $args );
 
    $filtered = $wp_filter[ $tag ]->apply_filters( $value, $args );
 
    array_pop( $wp_current_filter );
 
    return $filtered;
}

処理している内容としてはどちらも$wp_filterの中の関数をフックごとに登録されている関数分回して実行しています。ただしdo_actionはreturn していませんが、apply_filtersでは関数で処理した結果をreturnしています。

最後に

このように普段何気なく書いているコードをコアファイルから追っていくことでどのようなロジックで処理されているかが理解できるようになります。これを気に是非コピペ実装を卒業して頂ければと思います。

今回はいかがでしたでしょうか。

今後新規サイト構築やサーバの引っ越しを考えられている方がいらっしゃいましたら、是非日本に大規模なデータセンターを構え、手厚いサービスをしてくれることで定評のある鈴与シンワートの「S-Port」クラウドを導入してみてください。

また、併せてKUSANAGIというWordPressサイトをキャッシュ未使用でも高速化を可能とするサービスも是非使ってみてはいかがでしょうか。

ご興味がある方は以下をご参照ください。
https://s-port.shinwart.com/service/kusanagi/