分析 WordPress `wp_schedule_event()` 函数的源码:解释它如何在 `wp_options` 表中存储定时任务。

大家好,欢迎来到今天的“WordPress定时炸弹揭秘”讲座!今天咱们要聊聊WordPress的wp_schedule_event()函数,看看它背地里是怎么搞事情,把定时任务偷偷藏到数据库里的。

准备好了吗?咱们开始拆解这个“定时炸弹”!

一、定时任务,你瞅啥?瞅你咋滴!

首先,我们得搞清楚,什么是定时任务? 简单来说,就是让WordPress在未来的某个时间点自动执行一些代码。比如:

  • 定时发布文章: 设定好时间,WordPress自动帮你把文章发出去了。
  • 清理数据库: 定期清理那些过期的临时数据,让数据库保持苗条身材。
  • 发送邮件: 每天定时发送邮件通知用户。

这些任务都需要一个机制来告诉WordPress:“嘿,老兄,到点儿了,该干活儿了!” wp_schedule_event()就是负责安排这些活儿的“包工头”。

二、wp_schedule_event():包工头登场

wp_schedule_event()函数的原型是这样的:

/**
 * Schedules a hook to run at a specific time.
 *
 * @since 2.1.0
 *
 * @param int    $timestamp  A Unix timestamp that indicates when the event should run.
 * @param string $hook       The name of the action hook to execute.
 * @param array  $args       Optional. An array of arguments to pass to the hook's callback function.
 *                           Default empty array.
 *
 * @return bool True if the event was successfully scheduled, false otherwise.
 */
function wp_schedule_event( $timestamp, $hook, $args = array() ) {
    global $wp_filter;

    $timestamp = (int) $timestamp;

    /**
     * Fires just before a scheduled event is about to be scheduled.
     *
     * @since 2.1.0
     *
     * @param int    $timestamp A Unix timestamp that indicates when the event should run.
     * @param string $hook      The name of the action hook to execute.
     * @param array  $args      An array of arguments to pass to the hook's callback function.
     */
    do_action( 'schedule_event', $timestamp, $hook, $args );

    // Don't schedule events that occur in the past.
    if ( $timestamp < time() ) {
        return false;
    }

    $crons = _get_cron_array();
    $key = md5( $hook . serialize( $args ) );

    if ( isset( $crons[$timestamp][$key] ) ) {
        return false;
    }

    $crons[$timestamp][$key] = array(
        'hook' => $hook,
        'args' => $args,
        'schedule' => false, // 表示这是一个一次性事件,没有重复周期
    );

    uasort( $crons, 'sort_timestamp' );

    _set_cron_array( $crons );

    /**
     * Fires after a scheduled event is scheduled.
     *
     * @since 2.1.0
     *
     * @param int    $timestamp A Unix timestamp that indicates when the event should run.
     * @param string $hook      The name of the action hook to execute.
     * @param array  $args      An array of arguments to pass to the hook's callback function.
     */
    do_action( 'scheduled_event', $timestamp, $hook, $args );

    return true;
}

这个函数接收三个参数:

  • $timestamp: Unix时间戳,告诉WordPress什么时候执行这个任务。比如 time() + 3600 表示一个小时后。
  • $hook: 动作钩子(action hook)的名字。 当时间到了,WordPress会触发这个钩子,执行挂载到这个钩子上的函数。
  • $args: 传递给钩子函数的参数。 可以是一个数组,包含你需要传递给钩子函数的任何数据。

三、wp_options表:幕后大Boss

wp_schedule_event() 并没有直接往数据库里写数据。它只是把定时任务的信息添加到一个叫做$crons的数组里。 真正把这些信息存到数据库里的是 _set_cron_array() 函数。而_set_cron_array()函数最终会更新 wp_options 表里的 cron 选项。

wp_options表是WordPress用来存储各种配置信息的。 cron 选项就是用来存储定时任务的“秘密基地”。

四、_get_cron_array(): 获取定时任务

在将新的定时任务写入数据库之前,我们需要先从数据库中读取现有的定时任务。这个工作由_get_cron_array()函数完成。

/**
 * Retrieve cron info array.
 *
 * @since 2.1.0
 *
 * @return array Cron array.
 */
function _get_cron_array() {
    global $wpdb;

    $key = 'cron';

    $cron = wp_cache_get( $key, 'options' );

    if ( false === $cron ) {
        $cron = get_option( $key );

        if ( empty( $cron ) ) {
            $cron = array();
        }

        wp_cache_add( $key, $cron, 'options' );
    }

    return $cron;
}

这个函数首先尝试从WordPress的对象缓存中获取cron选项。如果缓存中没有,它会从wp_options表中读取。如果wp_options表中也没有cron选项,它会返回一个空数组。

五、_set_cron_array(): 保存定时任务

现在,我们来看_set_cron_array()函数,它负责将定时任务保存到数据库。

/**
 * Update cron info array.
 *
 * @since 2.1.0
 *
 * @param array $cron Cron array.
 */
function _set_cron_array( $cron ) {
    wp_cache_set( 'cron', $cron, 'options' );
    update_option( 'cron', $cron );
}

这个函数首先将cron数组保存到WordPress的对象缓存中,然后使用update_option()函数将cron数组更新到wp_options表中。

六、cron选项的数据结构:定时任务的藏宝图

cron选项存储的是一个序列化的数组。 这个数组的结构有点复杂,咱们来解剖一下:

array(
    [timestamp1] => array( // Unix时间戳,表示任务的执行时间
        [md5_hash1] => array( // 通过hook和args计算出的MD5哈希值,作为任务的唯一标识
            'hook' => 'my_custom_hook', // 动作钩子的名字
            'args' => array( 'arg1' => 'value1', 'arg2' => 'value2' ), // 传递给钩子函数的参数
            'schedule' => false, // 定时任务的类型 (false表示一次性任务)
        ),
        [md5_hash2] => array(
            'hook' => 'another_hook',
            'args' => array(),
            'schedule' => false,
        ),
    ),
    [timestamp2] => array(
        [md5_hash3] => array(
            'hook' => 'yet_another_hook',
            'args' => array( 'id' => 123 ),
            'schedule' => false,
        ),
    ),
);
  • 最外层数组的键: 是Unix时间戳,表示任务应该执行的时间。
  • 第二层数组的键: 是一个MD5哈希值,这个哈希值是通过hookargs计算出来的。 用来保证任务的唯一性。 如果hookargs相同,那么这个任务就会被认为是重复的,不会被添加。
  • 第二层数组的值: 是一个包含hookargsschedule的关联数组。
字段 含义
hook 动作钩子的名称,WordPress会在指定时间触发这个钩子。
args 传递给钩子函数的参数数组。
schedule 定时任务的类型。 false 表示一次性任务。 如果是重复任务,会是 ‘hourly’、’daily’、’weekly’ 等。

七、wp-cron.php:定时炸弹的引爆器

WordPress并不是真的“定时”执行任务。 它实际上是通过一个叫做wp-cron.php的文件来模拟定时任务的。

每次有人访问WordPress站点时,WordPress会检查是否需要执行任何定时任务。 如果需要,它就会调用wp-cron.php来执行这些任务。

wp-cron.php文件会:

  1. 加载WordPress核心文件: 这样才能访问数据库和各种函数。
  2. 获取cron选项: 从wp_options表里读取定时任务的信息。
  3. 检查是否有到期的任务: 遍历cron数组,找出时间戳小于当前时间的任务。
  4. 执行到期的任务: 循环遍历到期的任务,并使用do_action()函数触发相应的动作钩子。
  5. 清理已执行的任务: 从cron数组中删除已经执行过的任务(对于一次性任务)。
  6. 更新cron选项: 把更新后的cron数组存回wp_options表。

八、代码示例:安排一场约会

咱们来写一段代码,演示如何使用wp_schedule_event()来安排一个定时任务。

<?php
/**
 * 安排一个定时任务,在10分钟后执行 my_custom_function 函数.
 */
function schedule_my_event() {
    // 计算10分钟后的时间戳
    $timestamp = time() + (10 * 60);

    // 如果事件没有被安排过,则安排事件
    if ( ! wp_next_scheduled( 'my_custom_hook' ) ) {
        wp_schedule_event( $timestamp, 'my_custom_hook', array( 'user_id' => 123 ) );
    }
}
add_action( 'init', 'schedule_my_event' );

/**
 * 定时任务要执行的函数.
 *
 * @param array $args 传递过来的参数.
 */
function my_custom_function( $args ) {
    // 在这里编写你要执行的代码
    // 比如,发送一封邮件,或者更新数据库
    $user_id = $args['user_id'];
    wp_mail( '[email protected]', '定时任务执行了!', 'User ID: ' . $user_id );
    error_log('定时任务执行了!User ID: ' . $user_id); //写入错误日志,方便调试
}
add_action( 'my_custom_hook', 'my_custom_function' );

这段代码做了以下几件事:

  1. schedule_my_event()函数
    • 计算10分钟后的时间戳。
    • 使用wp_schedule_event()函数安排一个定时任务,在10分钟后执行my_custom_hook 钩子。
    • 传递一个包含user_id的数组作为参数。
    • 使用wp_next_scheduled()函数检查是否已经安排了名为 my_custom_hook 的事件。 如果没有安排过,才安排事件,避免重复安排。
  2. my_custom_function()函数
    • 是定时任务要执行的函数。
    • 接收传递过来的参数 $args
    • 发送一封邮件,并在错误日志中写入一条消息,表示任务已经执行。

九、手动触发定时任务

有时候,你可能需要手动触发定时任务,而不需要等待WordPress自动执行。 这可以通过访问wp-cron.php文件来实现。

在浏览器中输入你的WordPress站点的URL,后面加上/wp-cron.php。例如:

http://your-website.com/wp-cron.php

访问这个URL会强制WordPress执行所有到期的定时任务。

十、一些注意事项

  • 不要过度使用定时任务: 过多的定时任务会影响网站的性能,尤其是如果你的服务器配置不高。
  • 选择合适的定时任务类型: 如果你的任务只需要执行一次,就使用一次性任务。 如果需要定期执行,就选择合适的重复周期(hourly, daily, weekly等)。
  • 注意时区问题: 确保你的服务器时区和WordPress时区设置正确,否则定时任务可能会在错误的时间执行。
  • 使用wp_clear_scheduled_hook()函数取消定时任务: 如果你不再需要某个定时任务,可以使用wp_clear_scheduled_hook()函数来取消它。

十一、取消定时任务

如果你想取消一个已经安排的定时任务,可以使用 wp_clear_scheduled_hook() 函数。

/**
 * Unschedules a hook that is scheduled to run at a specific time.
 *
 * @since 2.1.0
 *
 * @param string $hook The name of the action hook to unschedule.
 * @param array  $args Optional. An array of arguments to pass to the hook's callback function.
 *                     Default empty array.
 *
 * @return bool True when the event is unscheduled, false otherwise.
 */
function wp_clear_scheduled_hook( $hook, $args = array() ) {
    $crons = _get_cron_array();
    $key = md5( $hook . serialize( $args ) );

    foreach ( $crons as $timestamp => $cron ) {
        if ( isset( $cron[$key] ) ) {
            unset( $crons[$timestamp][$key] );
            if ( empty( $crons[$timestamp] ) ) {
                unset( $crons[$timestamp] );
            }
            _set_cron_array( $crons );
            return true;
        }
    }

    return false;
}

这个函数接收两个参数:

  • $hook: 要取消的钩子的名称。
  • $args: 传递给钩子函数的参数数组。 必须和安排定时任务时使用的参数完全一致,才能正确取消。

例如,要取消我们之前安排的 my_custom_hook 定时任务,可以这样写:

wp_clear_scheduled_hook( 'my_custom_hook', array( 'user_id' => 123 ) );

十二、重复性定时任务

除了wp_schedule_event()之外,WordPress还提供了wp_schedule_event()的进化版:wp_schedule_recurring_event(),用于创建重复性的定时任务。

/**
 * Schedules a hook to run repeatedly.
 *
 * @since 2.1.0
 *
 * @param int    $timestamp A Unix timestamp that indicates when the first event should run.
 * @param string $recurrence How often the event should subsequently recur. See {@link wp_get_schedules()}.
 * @param string $hook       The name of the action hook to execute.
 * @param array  $args       Optional. An array of arguments to pass to the hook's callback function.
 *                           Default empty array.
 *
 * @return bool True if the event was successfully scheduled, false otherwise.
 */
function wp_schedule_recurring_event( $timestamp, $recurrence, $hook, $args = array() ) {
    global $wp_filter;

    $timestamp = (int) $timestamp;

    /**
     * Fires just before a recurring event is about to be scheduled.
     *
     * @since 2.1.0
     *
     * @param int    $timestamp A Unix timestamp that indicates when the first event should run.
     * @param string $recurrence How often the event should subsequently recur. See {@link wp_get_schedules()}.
     * @param string $hook      The name of the action hook to execute.
     * @param array  $args      An array of arguments to pass to the hook's callback function.
     */
    do_action( 'schedule_event', $timestamp, $recurrence, $hook, $args );

    // Don't schedule events that occur in the past.
    if ( $timestamp < time() ) {
        return false;
    }

    $crons = _get_cron_array();
    $key = md5( $hook . serialize( $args ) );

    if ( isset( $crons[$timestamp][$key] ) ) {
        return false;
    }

    $crons[$timestamp][$key] = array(
        'hook' => $hook,
        'args' => $args,
        'schedule' => $recurrence, // 关键区别:这里存储了重复周期
    );

    uasort( $crons, 'sort_timestamp' );

    _set_cron_array( $crons );

    /**
     * Fires after a recurring event is scheduled.
     *
     * @since 2.1.0
     *
     * @param int    $timestamp A Unix timestamp that indicates when the first event should run.
     * @param string $recurrence How often the event should subsequently recur. See {@link wp_get_schedules()}.
     * @param string $hook      The name of the action hook to execute.
     * @param array  $args      An array of arguments to pass to the hook's callback function.
     */
    do_action( 'scheduled_event', $timestamp, $recurrence, $hook, $args );

    return true;
}

这个函数与wp_schedule_event()类似,但增加了一个$recurrence参数,用于指定重复周期。 WordPress预定义了一些重复周期,你可以使用wp_get_schedules()函数获取这些周期。

/**
 * Retrieve supported recurrence schedules.
 *
 * @since 2.1.0
 *
 * @return array Named array of schedule names suitable for display.
 */
function wp_get_schedules() {
    $schedules = array(
        'hourly'     => array(
            'interval' => HOUR_IN_SECONDS,
            'display'  => __( 'Once Hourly' ),
        ),
        'twicedaily' => array(
            'interval' => 12 * HOUR_IN_SECONDS,
            'display'  => __( 'Twice Daily' ),
        ),
        'daily'      => array(
            'interval' => DAY_IN_SECONDS,
            'display'  => __( 'Once Daily' ),
        ),
        'weekly'     => array(
            'interval' => WEEK_IN_SECONDS,
            'display'  => __( 'Once Weekly' ),
        ),
    );

    /**
     * Filters the cron schedules available in WordPress.
     *
     * @since 2.1.0
     *
     * @param array $schedules An associative array of schedules keyed by schedule name.
     */
    return apply_filters( 'cron_schedules', $schedules );
}

wp_get_schedules()函数返回一个包含预定义重复周期的数组,例如hourlydailyweekly等。你也可以通过cron_schedules过滤器添加自定义的重复周期。

十三、代码示例:安排一个每日任务

<?php
/**
 * 安排一个每日定时任务,在每天的上午8点执行 my_daily_function 函数.
 */
function schedule_my_daily_event() {
    // 计算今天上午8点的时间戳
    $timestamp = strtotime( 'today 8:00' );

    // 如果今天上午8点已经过去了,则安排明天的上午8点
    if ( $timestamp < time() ) {
        $timestamp = strtotime( 'tomorrow 8:00' );
    }

    // 如果事件没有被安排过,则安排事件
    if ( ! wp_next_scheduled( 'my_daily_hook' ) ) {
        wp_schedule_recurring_event( $timestamp, 'daily', 'my_daily_hook', array( 'site_url' => get_site_url() ) );
    }
}
add_action( 'init', 'schedule_my_daily_event' );

/**
 * 每日定时任务要执行的函数.
 *
 * @param array $args 传递过来的参数.
 */
function my_daily_function( $args ) {
    // 在这里编写你要执行的代码
    // 比如,备份数据库,或者发送每日报告
    $site_url = $args['site_url'];
    wp_mail( '[email protected]', '每日定时任务执行了!', 'Site URL: ' . $site_url );
    error_log('每日定时任务执行了!Site URL: ' . $site_url); //写入错误日志,方便调试
}
add_action( 'my_daily_hook', 'my_daily_function' );

这段代码与之前的示例类似,但使用了wp_schedule_recurring_event()函数来安排一个每日定时任务。 任务会在每天的上午8点执行my_daily_hook钩子。

十四、总结

好了,今天的“WordPress定时炸弹揭秘”讲座就到这里了。 希望通过今天的讲解,你已经对wp_schedule_event()函数的工作原理有了更深入的了解。

记住,定时任务是一把双刃剑。 合理使用可以提高网站的自动化程度,但过度使用会影响网站的性能。 所以,一定要谨慎使用,并定期检查和清理你的定时任务。

如果还有什么疑问,欢迎随时提问。 感谢大家的参与!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注