森下竜行の〇〇ってなんやねん!

著:森下竜行氏

WP-Cronってなんやねん第12回 19年10月更新

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

今回はWP-Cronという機能について詳しく解説していきたいと思います。 

1. WP-Cronとは?

WordPressがWordPressで時間ベースのタスクをスケジュールする仕組みのことです。
更新の確認や予約投稿等、WordPressのいくつかのコア機能は、WP-Cronを利用しています。

2. WP-Cronの特徴

Linuxサーバーの場合は、システムCronを使ってスケジュール管理することもでできるのですが、
サーバーをいじることができない等Cronを使えない環境もあります。

このような状況の場合WP-Cronを使うと簡単にスクリプト実行のスケジュール管理をすることができます。

・指定時間を過ぎた最初のアクセス時に実行される
・そのため、指定時間に必ず実行される保証はない

そのため、アクセスがあまりないサイトでは実行が遅延しがちになるという点に注意です。
また、ローカル開発環境だと正常に動作しないことがあるようです。
ちなみにスケジュールタスクを実行するUNIXプログラム、crontabにちなんで命名されています。

3.実行プロセスについて

仕組み自体はある程度理解しているつもりでしたが、コアファイルを追って見たことがなかったため
コアファイルのどこで読み込まれているかを確認していきます。ちなみに今回確認したWordPressのバージョンは5.2.2です。

①まずドキュメントルート直下のindex.phpが読み込まれます。
②次にwp-blog-header.phpが読み込まれます。
③次にwp-blog-header.php内でwp-config.phpが読み込まれます。
④次にwp-config.php内でwp-settings.phpが読み込まれます。
⑤wp-includesディレクトリ内のdefault-filters.phpというファイルが読み込まれます。
⑥wp-includesディレクトリ内のdefault-filters.php内を確認すると

 

// WP Cron                                                                                                                                                                                                                                   
if ( ! defined( 'DOING_CRON' ) ) {                                                                                                                                                                                                           
    add_action( 'init', 'wp_cron' );                                                                                                                                                                                                         
}

 

という記述が確認できます。

このwp_cronという関数がどこのファイルに定義されているかをgrepコマンドで探すと
wp-includesディレクトリ内のcron.phpというファイル内で定義されています。

⑦initのタイミングでwp_cron関数が実行されます。

 

function wp_cron() {                                                                                                                                                                                                                         
    // Prevent infinite loops caused by lack of wp-cron.php                                                                                                                                                                                  
    if ( strpos( $_SERVER['REQUEST_URI'], '/wp-cron.php' ) !== false || ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) ) {                                                                                                              
        return 0;                                                                                                                                                                                                                            
    }                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
    $crons = wp_get_ready_cron_jobs();                                                                                                                                                                                                       
    if ( empty( $crons ) ) {                                                                                                                                                                                                                 
        return 0;                                                                                                                                                                                                                            
    }                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
    $gmt_time = microtime( true );                                                                                                                                                                                                           
    $keys     = array_keys( $crons );                                                                                                                                                                                                        
    if ( isset( $keys[0] ) && $keys[0] > $gmt_time ) {                                                                                                                                                                                       
        return 0;                                                                                                                                                                                                                            
    }                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                             
    $schedules = wp_get_schedules();                                                                                                                                                                                                         
    $results   = array();                                                                                                                                                                                                                    
    foreach ( $crons as $timestamp => $cronhooks ) {                                                                                                                                                                                         
        if ( $timestamp > $gmt_time ) {                                                                                                                                                                                                      
            break;                                                                                                                                                                                                                           
        }                                                                                                                                                                                                                                    
        foreach ( (array) $cronhooks as $hook => $args ) {                                                                                                                                                                                   
            if ( isset( $schedules[ $hook ]['callback'] ) && ! call_user_func( $schedules[ $hook ]['callback'] ) ) {                                                                                                                         
                continue;                                                                                                                                                                                                                    
            }                                                                                                                                                                                                                                
            $results[] = spawn_cron( $gmt_time );                                                                                                                                                                                            
            break 2;                                                                                                                                                                                                                         
        }
    }

    if ( in_array( false, $results, true ) ) {
        return false;
    }
    return count( $results );
}

 

⑧wp_cron関数の中身を見ていきます。細かい制御等部分の説明は省きます。
まずwp_get_ready_cron_jobsという関数を使用して$cronsという変数に格納しています。
wp_get_ready_cron_jobs関数は同じcron.phpファイル内で定義されています。

下記がwp_get_ready_cron_jobs関数の中身です。

 

function wp_get_ready_cron_jobs() {
    /**
     * Filter to preflight or hijack retrieving ready cron jobs.
     *
     * Returning an array will short-circuit the normal retrieval of ready
     * cron jobs, causing the function to return the filtered value instead.
     *
     * @since 5.1.0
     *
     * @param null|array $pre Array of ready cron tasks to return instead. Default null
     *                        to continue using results from _get_cron_array().
     */
    $pre = apply_filters( 'pre_get_ready_cron_jobs', null );
    if ( null !== $pre ) {
        return $pre;
    }

    $crons = _get_cron_array();

    if ( false === $crons ) {
        return array();
    }

    $gmt_time = microtime( true );
    $keys     = array_keys( $crons );
    if ( isset( $keys[0] ) && $keys[0] > $gmt_time ) {
        return array();
    }

    $results = array();
    foreach ( $crons as $timestamp => $cronhooks ) {
        if ( $timestamp > $gmt_time ) {
            break;
        }
        $results[ $timestamp ] = $cronhooks;
    }
    return $results;
}

 

ここでは登録されているジョブをループで回し、実行可能なジョブのみ変数$resultsに格納しています。
言い換えると過去のタイムスタンプに限定して変数に格納しています。
ちなみWP-Cronに登録されているジョブはwp_optionsというテーブルにシリアライズ化されて格納されており、
この関数内では_get_cron_arrayという関数でwp_optionsからジョブを取得し$cronsという変数に格納しています。

それでは再度関数wp_cronに戻ります。
wp_get_ready_cron_jobs関数で取得した関数$cronsをループで回し、
spawn_cronという関数にたどり着きます。
この関数内の

 

$result = wp_remote_post( $cron_request['url'], $cron_request['args'] );

 

という記述部分でドキュメントルート直下のwp-cron.phpにPOSTメソッドを送信しています。
これをトリガーに実行可能なジョブがループ処理され実行されているようです。
ちなみにwp-cron.phpの冒頭にある

ignore_user_abort(true);

はwp-cron.phpはクライアントの接続が切断されても、​処理を中断しないために
記述されています。

4.使う必要がなければ無効化しておく

もしWP-Cronを使用していないようであればwp-config.php

define('DISABLE_WP_CRON', true);

と記述しておいたほうがいいかと思います。
そうすれば余計な処理が走りません。

また使用する場合でも、Linux上のcronが使用できる環境であれば
cronにwp-cron.phpを登録しておいたほうが良いかと思います。
そうすることで指定時間に必ず処理が実行されます。

 

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

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

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

ご興味がある方は以下をご参照ください。

https://s-port.shinwart.com/service/kusanagi/