Categories
コラム

フックをコアから理解

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

突然ですが、この度共著で『ビジネスサイトを作って学ぶ 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に記述したことがあるかと思います。

サンプル①

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

サンプル②

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コマンドを実行してどこに定義されているか検索します。

grep -inr "function" | grep "add_action"

すると

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行目に該当箇所があることが分かります。早速覗いてみましょう。

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関数の内部をみてみましょう。

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は何やら処理をやっているので、順を追ってみていきましょう。

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

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

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

grep -inr "class" | grep WP_Hook

すると

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を確認します。

    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を覗いてみると上部で登録した関数が登録されていることが確認できます。

サンプル①

  ["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)
  }
}

サンプル②

  ["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です。

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 );

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/
Categories
コラム

Protected: WP-Cronってなんやねん

This content is password protected. To view it please enter your password below:

Categories
コラム

Protected: WP-Cronってなんやねん

This content is password protected. To view it please enter your password below:

Categories
コラム

次世代のAdvanced Custom Fields

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

今回は次世代のAdvanced Custom Fieldsと思った「Block Lab」というプラグインについて紹介したいと思います。

カスタムブロックについて

プラグインの説明の前に前提知識のカスタムブロックについて簡単に説明します。新エディターのGutenbergではブロックという新しい概念が導入され、見出し、文章、画像などの要素をレゴブロックの様に組み合わせて記事を作成します。カスタムブロックはその名の通りで独自に作成したブロックのことです。Block Labというプラグインは独自のブロックを管理画面に簡単に追加することができます。

今まではショートコードやクイックタグ、Advanced Custom Fieldsをを使用して管理画面を拡張していましたが、今後カスタムブロックがそれらの代替機能として活躍していくのではないでしょうか。

Block Labの使い方

使い方はほとんどAdvanced Custom Fieldsと同じです。サンプルで1つブロックを作成してきます。まず「Add New」をクリックしてブロックを追加します。

次にブロック名とフィールドを追加していきます。

まずブロック名に「おすすめ商品」と入力して、次に「Add Field」をクリックして具体的フィールドの値を入力していきます。1つ目のフィールドは上記の通りに埋めてください。

それが完了したら続けて「Add Field」をクリックし、上記の画像の通りにもう2つフィールドを作成してください。

上記のように必要なブロックフィールドを作成したら「公開」を押して作成したブロックを有効化します。しかしこれだけでは当然フロントに表示されるようになりません。上記の画像にも表示されている通り作成したブロック用にテンプレートを作成する必要があります。そのテンプレートファイルを参照してフロントに表示されるようになります。それでは早速作成していきます。

// wp-content/theme/使用しているテーマ/blocks/block-product.php

<div class="product">
	<h3><?php block_field( 'product-name' ); ?></h3>
<?php
// 画像があれば表示
$image = block_field( 'product-image', false ); 
if ( $image ) {
	echo wp_get_attachment_image( $image );
}
?>
	<p>
<?php
$product_information = block_value( 'product-detail' );
// デフォルトでは改行されないため下記を記述
echo nl2br($product_information); /
?>
	</p>
</div>

使用しているテーマ配下にblocksというディレクトリを作成し、その中にblock-product.phpというファイルを作成し、そのファイル内に上記のコードを記述してください。そして先ほどのブロック編集画面に戻ってみてください。

すると先ほどのアラートは消えているので、きちんと先ほど作成したファイルが認識されていることが確認できます。そして管理画面「投稿」> 「新規追加」から先ほど作成したブロックを追加しきちんと表示されているかを確認します。

「ブロックを追加」をクリックし一般ブロックを覗くと「おすすめの商品」がちゃんと認識されてブロックとして保存されています。そしてクリックし、詳細な情報を入力していきます。

入力が完了するとプレビューをクリックして表示されているかを確認します。

きちんと表示されていることが確認できます。

保存されるテーブル

最初はwp_postmetaに保存されていると思ったらwp_postsのpost_content内にシリアライズ化されて保存されていました。すごく意外だったため一応記述しておきました。

*************************** 1. row ***************************
                   ID: 781
          post_author: 1
            post_date: 2019-06-01 09:35:21
        post_date_gmt: 2019-06-01 00:35:21
         post_content: <!-- wp:block-lab/product {"product-name":"新商品〇〇","product-image":813,"product-detail":"つい昨日発売されましたー!"} /-->
           post_title: テスト
(略)

使ってみて感想

使いやすく未来感があるなとは思いましたが、ブロックを作成するごとにテンプレートファイルを作成したりUI等を鑑みるとまだまだAdvanced Custom Fieldsの方が使いやすいし便利だなと思いました。しかしブロック機能はGutenbergの醍醐味的要素なので自ずとカスタムブロックが主流になっていくんじゃないかなぁと思っています。
一応公式サイト、ドキュメントのリンクを下記に貼っておきます。
公式サイト:https://getblocklab.com/
ドキュメント:https://github.com/getblocklab/block-lab/wiki/

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

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

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