2026年8月11日

2026年8月11日

WP-Cronを使ったスケジュール処理の実装方法

はじめに

WP-Cronは、WordPressに組み込まれた擬似cronシステムです。メール送信、データベースクリーンアップ、外部APIとの同期など、定期的な処理を実装するために使います。ただし、WP-Cronはサイトへのアクセスがあるときにしか実行されない擬似cronのため、本番環境では実際のサーバーcronと組み合わせることが推奨されます。本記事では、毎日のレポートメール送信とデータベースクリーンアップを例に、WP-Cronの完全な実装パターンを解説します。

症状・原因

WP-Cronで起きやすい問題と対処が必要なケース:

  • スケジュールされた処理が実行されない(アクセスが少ないサイトでWP-Cronが発火しない)
  • プラグインを無効化してもcronイベントが残留してデータベースを肥大化させる
  • 標準の実行間隔(hourly/daily/twicedaily)では要件を満たせない
  • 重いバッチ処理でタイムアウトが発生する
  • cronの実行状況をデバッグ・モニタリングしたい

解決手順

ステップ1:カスタムインターバルの追加とスケジュール登録

cron_schedulesフィルターで独自の実行間隔を追加し、プラグイン有効化時にスケジュールを設定します。

<?php
/**
 * カスタムcronインターバルを追加する
 *
 * @param array $schedules 既存のスケジュール配列
 * @return array インターバルを追加した配列
 */
function my_plugin_add_cron_intervals(array $schedules): array {
    // 15分ごと
    $schedules['every_15_minutes'] = [
        'interval' => 15 * MINUTE_IN_SECONDS,
        'display'  => esc_html__('15分ごと', 'my-plugin')
    ];

    // 週1回(月曜日基準)
    $schedules['weekly'] = [
        'interval' => WEEK_IN_SECONDS,
        'display'  => esc_html__('週1回', 'my-plugin')
    ];

    return $schedules;
}
add_filter('cron_schedules', 'my_plugin_add_cron_intervals');

/**
 * プラグイン有効化時にcronイベントをスケジュールする
 */
function my_plugin_activate(): void {
    // 毎日の日次レポートをスケジュール(既に登録済みの場合はスキップ)
    if (!wp_next_scheduled('my_plugin_daily_report')) {
        wp_schedule_event(
            strtotime('tomorrow 06:00:00'), // 翌日の午前6時から開始
            'daily',                         // 毎日実行
            'my_plugin_daily_report'         // フックの名前
        );
    }

    // データベースクリーンアップを15分ごとに実行
    if (!wp_next_scheduled('my_plugin_db_cleanup')) {
        wp_schedule_event(
            time(),
            'every_15_minutes',
            'my_plugin_db_cleanup'
        );
    }
}
register_activation_hook(__FILE__, 'my_plugin_activate');

ステップ2:cronフック処理を実装

実際の処理をフックに登録します。重い処理は分割して実行することを考慮します。

<?php
/**
 * 日次レポートメールを送信する
 */
function my_plugin_send_daily_report(): void {
    // 処理開始をログ記録
    $start_time = microtime(true);
    error_log('[my_plugin_cron] 日次レポート処理開始: ' . date('Y-m-d H:i:s'));

    // 前日の統計データを集計
    $yesterday   = date('Y-m-d', strtotime('-1 day'));
    $stats        = my_plugin_get_daily_stats($yesterday);

    if (empty($stats)) {
        error_log('[my_plugin_cron] 前日データなし。スキップ。');
        return;
    }

    // メール本文を作成
    $subject = sprintf('[%s] 日次レポート %s', get_bloginfo('name'), $yesterday);
    $message = sprintf(
        "本日の集計レポートをお送りします。\n\n" .
        "期間: %s\n" .
        "新規注文数: %d\n" .
        "売上合計: ¥%s\n" .
        "新規ユーザー: %d名\n\n" .
        "詳細は管理画面をご確認ください: %s",
        $yesterday,
        $stats['order_count'],
        number_format($stats['total_revenue']),
        $stats['new_users'],
        admin_url('admin.php?page=my-plugin-report')
    );

    // 管理者メールアドレスに送信
    $admin_email = get_option('admin_email');
    $sent        = wp_mail($admin_email, $subject, $message);

    $elapsed = round(microtime(true) - $start_time, 3);
    error_log(sprintf(
        '[my_plugin_cron] 日次レポート完了: %s (%.3f秒)',
        $sent ? '送信成功' : '送信失敗',
        $elapsed
    ));
}
add_action('my_plugin_daily_report', 'my_plugin_send_daily_report');

/**
 * 前日の統計データを取得する
 *
 * @param string $date 日付 (Y-m-d)
 * @return array 統計データ
 */
function my_plugin_get_daily_stats(string $date): array {
    global $wpdb;

    // キャッシュキーを生成
    $cache_key = 'my_plugin_stats_' . $date;
    $cached    = get_transient($cache_key);

    if ($cached !== false) {
        return $cached;
    }

    $stats = [
        'order_count'   => (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->posts}
             WHERE post_type = 'shop_order'
             AND post_status IN ('wc-completed', 'wc-processing')
             AND DATE(post_date) = %s",
            $date
        )),
        'total_revenue' => (float) ($wpdb->get_var($wpdb->prepare(
            "SELECT SUM(pm.meta_value) FROM {$wpdb->postmeta} pm
             INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
             WHERE pm.meta_key = '_order_total'
             AND p.post_type = 'shop_order'
             AND DATE(p.post_date) = %s",
            $date
        )) ?? 0),
        'new_users'     => (int) $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$wpdb->users}
             WHERE DATE(user_registered) = %s",
            $date
        ))
    ];

    // 1時間キャッシュ
    set_transient($cache_key, $stats, HOUR_IN_SECONDS);

    return $stats;
}

ステップ3:データベースクリーンアップ処理を実装

期限切れトランジェントや古いログの削除処理を実装します。

<?php
/**
 * データベースのクリーンアップを実行する
 */
function my_plugin_run_db_cleanup(): void {
    global $wpdb;

    $deleted_count = 0;

    // 1. 期限切れトランジェントを削除
    $expired_transients = $wpdb->query(
        "DELETE FROM {$wpdb->options}
         WHERE option_name LIKE '%\_transient\_timeout\_%'
         AND option_value < UNIX_TIMESTAMP(NOW())"
    );

    // 関連するトランジェント値も削除
    $wpdb->query(
        "DELETE FROM {$wpdb->options}
         WHERE option_name LIKE '%\_transient\_%'
         AND option_name NOT LIKE '%\_transient\_timeout\_%'
         AND REPLACE(option_name, '_transient_', '_transient_timeout_')
         NOT IN (SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE '%\_transient\_timeout\_%')"
    );

    $deleted_count += (int) $expired_transients;

    // 2. 古いカスタムログレコードを削除(30日以上前)
    $deleted_logs = $wpdb->query(
        $wpdb->prepare(
            "DELETE FROM {$wpdb->prefix}my_plugin_logs
             WHERE created_at < %s
             LIMIT 500",
            date('Y-m-d H:i:s', strtotime('-30 days'))
        )
    );

    $deleted_count += (int) $deleted_logs;

    // 3. 自動下書きを削除(7日以上前)
    $wpdb->query(
        $wpdb->prepare(
            "DELETE FROM {$wpdb->posts}
             WHERE post_status = 'auto-draft'
             AND post_modified < %s
             LIMIT 200",
            date('Y-m-d H:i:s', strtotime('-7 days'))
        )
    );

    error_log(sprintf('[my_plugin_cron] DBクリーンアップ完了: %d件削除', $deleted_count));
}
add_action('my_plugin_db_cleanup', 'my_plugin_run_db_cleanup');

ステップ4:プラグイン無効化時のクリーンアップ

register_deactivation_hookでcronイベントを確実に削除します。

<?php
/**
 * プラグイン無効化時にcronイベントをすべて削除する
 */
function my_plugin_deactivate(): void {
    // 登録したすべてのcronフックをクリア
    $hooks = [
        'my_plugin_daily_report',
        'my_plugin_db_cleanup',
    ];

    foreach ($hooks as $hook) {
        // スケジュール済みのタイムスタンプを取得してから削除
        $timestamp = wp_next_scheduled($hook);

        if ($timestamp) {
            wp_unschedule_event($timestamp, $hook);
        }

        // 念のため全スケジュールを削除(再帰的に登録されていた場合)
        wp_clear_scheduled_hook($hook);
    }

    error_log('[my_plugin_cron] すべてのcronイベントを削除しました。');
}
register_deactivation_hook(__FILE__, 'my_plugin_deactivate');

/**
 * デバッグ用:次回実行時刻を確認する
 * 管理バーに追加する場合は is_user_logged_in() + current_user_can() でガードすること
 */
function my_plugin_get_cron_status(): array {
    return [
        'daily_report_next' => wp_next_scheduled('my_plugin_daily_report')
            ? date('Y-m-d H:i:s', wp_next_scheduled('my_plugin_daily_report'))
            : 'スケジュールなし',
        'db_cleanup_next'   => wp_next_scheduled('my_plugin_db_cleanup')
            ? date('Y-m-d H:i:s', wp_next_scheduled('my_plugin_db_cleanup'))
            : 'スケジュールなし',
    ];
}

ステップ5:DISABLE_WP_CRONとサーバーcronの連携設定

本番環境でWP-Cronを無効化し、サーバーのcronジョブと連携します。

// wp-config.php に追加してWP-Cronを無効化
define('DISABLE_WP_CRON', true);

// ALTERNATE_WP_CRON を使うと非同期リクエストで実行(共有ホストで有効)
// define('ALTERNATE_WP_CRON', true);
# サーバーのcrontabに追加(crontab -e で編集)
# 毎分WordPressのcronを実行
* * * * * /usr/bin/php /var/www/html/wp-cron.php > /dev/null 2>&1

# wp-cli を使う方法(推奨)
* * * * * cd /var/www/html && wp cron event run --due-now --quiet 2>&1 | logger -t wp-cron

# cronの実行ログを確認
# tail -f /var/log/syslog | grep wp-cron

# 手動でcronを実行してテスト
# wp cron event run my_plugin_daily_report
# wp cron event list  # 登録済みイベント一覧

注意事項

  • wp_schedule_event() の第1引数はUNIXタイムスタンプであり、time() を渡すと即時開始になる。特定の時刻から開始したい場合は strtotime() で計算すること
  • wp_next_scheduled() で重複チェックを必ず行うこと(チェックなしで複数回プラグインを有効化すると同一フックが重複登録される)
  • cronフック内では set_time_limit(0) や処理の分割を検討し、PHPのmax_execution_timeによるタイムアウトを防ぐ
  • DISABLE_WP_CRONtrue にした場合、サーバーcronの設定を怠るとスケジュールが一切実行されなくなる
  • WP-CLIの wp cron event list コマンドは、登録済みのcronイベントとその次回実行時刻を確認するデバッグに非常に有効

まとめ

WP-Cronはwp_schedule_event()add_action()の組み合わせで動作し、プラグインのライフサイクル(有効化・無効化)に合わせて適切に登録・削除することが重要です。本番環境ではDISABLE_WP_CRONとサーバーcronの組み合わせが信頼性の高い実行を保証します。定期処理の中でデータを効率的に扱うには、Transientキャッシュによる高速化テクニックも合わせて実装することをお勧めします。

お気軽にご相談ください

お見積りへ お問い合わせへ