WordPress как на ладони
Недорогой хостинг для сайтов на WordPress: wordpress.jino.ru

WP Cron в WordPress (планировщик задач)

Допустим, нам нужно, чтобы через 5 часов выполнилась какая-то PHP функция. Или нужно, чтобы эта функция выполнялась каждый день. Решить такие задачи поможет WordPress Крон — планировщик задач. Ниже, давайте разберемся как он работает и как его использовать.

Название Cron взято из UNIX-подобных операционных систем. Там Cron — это планировщик заданий в задачу которого входит периодическое выполнение указанных действий в определённое время.

Kama_Cron — маленький класс для удобной работы с Кроном WordPress.

Крон задачи самого WordPress

WordPress использует крон для своих нужд, так в нем есть базовые крон задачи:

меню

Как работает крон (шаг за шагом)

  1. При посещении любой страницы сайта (при любом запросе к сайту), в том числе при AJAX, REST запросах, т.е. всегда на событии init срабатывает функция wp_cron().

    if ( ! defined( 'DOING_CRON' ) )
    	add_action( 'init', 'wp_cron' );
  2. wp_cron() проверяет существует ли хоть одно задание с подошедшем временем. Если есть, то вызывает функцию spawn_cron().

  3. spawn_cron() запускает крон! Отправляет не блокирующий HTTP запрос на файл крона /wp-cron.php в котором передает текущую метку времени вида: microtime(true), например: 1538326680.8330409526824951171875.

    Тут проверяется, когда был в последний раз запущен крон, если менее 60 секунд, то функция ничего не делает. Изменить этот интервал можно через константу WP_CRON_LOCK_TIMEOUT. Указать в константе можно максимум 600 (10 минут), если указать больше, то она будет игнорироваться. Пример:

    define( 'WP_CRON_LOCK_TIMEOUT', 60 )

    Далее проверяется есть ли хоть одно подошедшее задание, если есть то во временную опцию 'doing_cron' записывается текущая метка времени, когда был запущен крон и отправляется неблокирующий запрос на файл /wp-cron.php, там эта метка времени используется для разных проверок.

  4. Файл /wp-cron.php опять проверяет: не запускался ли недавно крон (менее 60 сек назад), есть ли подошедшие крон задания.

    Далее, проходит по всем «поспевшим» задачам и для каждого из них: удаляет текущую задачу из расписания, и если у задачи есть метка schedule, (оно повторяющееся), создает новое такое же задание с помощью функции wp_reschedule_event(). Новое задание будет выполнено через указанный в интервале промежуток времени.

    Далее, запускается указанный при регистрации крон задачи хук-событие, т.е. запускается текущее задание:

    do_action_ref_array( $hook, $v['args'] );

    Если обращаться к файлу /wp-cron.php напрямую, то он будет отрабатывать только раз в WP_CRON_LOCK_TIMEOUT секунд (60 секунд).

    Если в кроне много заданий и одно задание начало выполняться и выполняется так долго, что уже был отправлен очередной запрос на выполнение заданий крон, то очередная задача в первом крон запросе НЕ будет выполнена — первый запрос просто остановится после выполнения «долгой» задачи и очередные задания уже будут выполняться во втором запросе.

Заметки

Все крон задачи хранятся в опции get_option( 'cron' ).

Крон запрос запускается отдельно от текущей загрузки страницы и в нем отдельно грузится среда ВП и т.д. Текущий запрос (посещение страницы) только инициализирует крон (создает запрос на файл крона), если время подошло.

Другими словами: крон задачи выполняются асинхронно. Т.е. для выполнения ожидаемых крон задач, WordPress отправляет запрос на файл http://example.com/wp-cron.php. Этот файл самодостаточный: он устанавливает константу define('DOING_CRON', true), затем подгружает среду WordPress и выполняет все ожидаемые задачи.

меню

Как проверить, работает ли крон на сайте?

Быстрый способ убедиться что крон исправно работает — это опубликовать запись с датой позже текущей (она будет поставлена в очередь на публикацию) и посмотреть опубликуется она или нет.

Если крон работает, то запись просто опубликуется в заданное время. А если крон не работает, то мы увидим такую картину:

Крон может не работать по двум причинам:

  • Он отключен. Как отключать смотрите ниже.
  • Ваш сервер не умеет отправлять HTTP запросы. Крон активируется через неблокирующий HTTP запрос WP к самому себе.
меню

Включение другого механизма запуска крона

По умолчанию крон запускается POST запросом, с помощью wp_remote_post() с любой посещенной страницы сайта, если время подошло. Но если у вас на сервере такой вариант не работает, то можно включить альтернативный вариант запуска крона. Для этого добавьте такую константу в файл wp-config.php:

define( 'ALTERNATE_WP_CRON', true );

Такой альтернативный запуск крона создаст запрос через wp_redirect(), т.е. запрос будет отправлен не из PHP, а клиентом (браузером). Подробнее см. spawn_cron().

меню

Как отключить крон?

Чтобы отключить крон, нужно зайти в файл wp-config.php и добавить туда следующую строчку:

define( 'DISABLE_WP_CRON', true );

Отключать крон крайне не рекомендуется, потому что через него WordPress чистит за собой всякие черновики и удаляет записи из корзины, также на базе крона могут работать некоторые плагины!

Функции крона

Добавление:

wp_schedule_event( $timestamp, $recurrence, $hook, $args )
Создает многоразовую крон-задачу. Устанавливает хук, который будет вызываться каждый раз через указанный интервал времени.
wp_schedule_single_event( $timestamp, $hook, $args )
Создает одноразовую крон-задачу. Устанавливает хук, который будет вызван всего один раз в указанное время.

Удаление:

wp_unschedule_event( $timestamp, $hook, $args )
Удаляет конкретную крон-задачу. Для удаления нужно знать все 3 параметра.
wp_clear_scheduled_hook( $hook, $args )
Отменяет (удаляет) запланированные cron задания по указанному имени хука и передаваемым параметрам. Работает на основе wp_unschedule_event().
wp_unschedule_hook( $hook )
Удаляет из расписания крон абсолютно все крон задачи по указанному хуку. Тут не важно какие параметры были указаны при регистрации задачи.

Остальное:

wp_next_scheduled( $hook, $args )
Возвращает метку времени (timestamp) когда должно сработать следующее по расписанию cron задание. Позволяет проверить есть ли в крон указанное задание.
wp_doing_cron()
Определяет является ли текущий запрос, запросом к Крону. Условный тег.
wp_get_schedules( )
Получает поддерживаемые Cron интервалы времени.

Весь список смотрите по этой ссылке.

меню

Создание крон задач

Для создания новых крон задач используется одна из функций: wp_schedule_event() или wp_schedule_single_event().

Вызывать функции создания крон задач нужно только один раз! Обычно это делается при активации плагина, а при деактивации эти крон задачи нужно удалять. Или можно сначала проверить нет ли уже указанной крон задачи, если нет, то добавить.

При вызове функции регистрации задачи она записывается в опцию сайта cron и работает уже от туда автономно.

Если задача есть в кроне, но она не срабатывает, значит во время крон запроса не подключается хук. Например, если выставлять задачу wp_schedule_single_event() через AJAX запрос и там же регистрировать хук этой задачи, то задача будет зарегистрирована в кроне, но функция в нужный момент выполняться не будет, потому что хук на который повешена функция срабатывает при AJAX запросе, а должен срабатывать при крон запросе! Поэтому сам хук нужно вешать в functions.php или в плагин или как-то еще, но не во время обработки аякс запроса.

Если при добавлении крон задачи, в планировщике уже есть задача с таким же названием и аргументами и время новой задачи не превышает 10 минут от времени имеющийся, то добавление новой крон-задачи будет проигнорировано.

Суть этого в том, чтобы не допустить планирования двух одинаковых событий в течение десяти минут друг от друга, но не препятствовать планированию одинаковых событий с разницей во времени больше десяти минут друг от друга.

События считаются одинаковыми, если совпадают: название хука и передаваемые параметры.

меню

Повторяющиеся задачи

#1 Создаем крон задачу при активации плагина

Запланируем ежечасное действие для плагина. Для этого вызовем wp_schedule_event() при активации плагина (если делать не при активации, то мы получим множество запланированных событий!).

register_activation_hook(__FILE__, 'my_activation');
function my_activation() {
	// удалим на всякий случай все такие же задачи cron, чтобы добавить новые с "чистого листа"
	// это может понадобиться, если до этого подключалась такая же задача неправильно (без проверки что она уже есть)
	wp_clear_scheduled_hook( 'my_hourly_event' );

	// Проверим нет ли уже задачи с таким же хуком
	// этот пункт не нужен, потому что мы выше удалил все задачи.
	// if( ! wp_next_scheduled( 'my_hourly_event' ) ) 

	// добавим новую cron задачу
	wp_schedule_event( time(), 'hourly', 'my_hourly_event');
}

add_action( 'my_hourly_event', 'do_this_hourly' );
function do_this_hourly() {
	// делаем что-либо каждый час
}

// При деактивации плагина, обязательно нужно удалить задачу:
register_deactivation_hook( __FILE__, 'my_deactivation' );
function my_deactivation(){
	wp_clear_scheduled_hook( 'my_hourly_event' );
}
меню

#2 Создаем крон задачу если её еще нет

Этот пример не полагается на активацию плагина (через директорию плагинов), вместо этого он добавляет событие, если его не существует.

// добавляет новую крон задачу
add_action( 'admin_head', 'my_activation' );
function my_activation() {
	if( ! wp_next_scheduled( 'my_hourly_event' ) ) {
		wp_schedule_event( time(), 'hourly', 'my_hourly_event');
	}
}

// добавляем функцию к указанному хуку
add_action( 'my_hourly_event', 'do_this_hourly' );
function do_this_hourly(){
	// делаем что-либо каждый час
}

Минус этого кода в том, что проверка происходит всегда при всех запросах, а добавляется задача всего один раз. Однако PHP затраты тут мизерные, сравнимые с обычным получением опции get_option(), поэтому на это можно закрыть глаза в угоду удобству.

меню

#3 Еще примеры

Смотрите в описании функции wp_schedule_event().

Одноразовые задачи

#1 Запланируем событие через час с текущего момента

// добавляет новую одноразовую крон задачу
add_action( 'admin_head', 'my_activation' );
function my_activation() {
	if( ! wp_next_scheduled( 'my_new_event' ) ) {
		wp_schedule_single_event( time() + 3600, 'my_new_event' );
		// time() + 3600 = 1 час с текущего момента.
	}
}

add_action( 'my_new_event','do_this_in_an_hour' );
function do_this_in_an_hour(){
	// делаем что-нибудь
}

#2 Еще примеры

Смотрите в описании функции wp_schedule_single_event().

Интервалы для крон задач

Крон интервалы нужны, чтобы они были и затем их можно было использовать при пере-регистрации крон-задачи.

По умолчанию в WP три интервала:

'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' ) ),

Добавлять новый интервал Cron нужно через фильтр cron_schedules.

Добавим интервал «5 минут» (делать что-либо каждые 5 минут):

// регистрируем 5минутный интервал
add_filter( 'cron_schedules', 'cron_add_five_min' );
function cron_add_five_min( $schedules ) {
	$schedules['five_min'] = array(
		'interval' => 60 * 5,
		'display' => 'Раз в 5 минут'
	);
	return $schedules;
}

Код создания интервала должен срабатывать всегда, так как данные об интервалах не хранятся в базе данных, а используются они постоянно. При выполнении одной крон задачи, WP создает такое же задание, а метку времени этого нового задания вычисляет на основе указанного имени интервала. И поэтому данные интервала нужны всегда!

Казалось бы почему просто не указать интервал при добавлении задачи и все, зачем нужно добавлять интервал через фильтр. Дело в том, что один и тот же интервал могут использовать разные крон задачи.

Теперь можно использовать этот интервал при создании крон-задачи:

// регистрируем событие
add_action( 'wp', 'my_activation' );
function my_activation() {
	if ( ! wp_next_scheduled( 'my_five_min_event' ) )
		wp_schedule_event( time(), 'five_min', 'my_five_min_event' );
}

// функция крон-задачи
add_action( 'my_five_min_event', 'do_every_five_min' );
function do_every_five_min(){
	// делаем что-либо каждые 5 минут
}

Константы WordPress которые могут пригодится при создании крон интервала:

  • HOUR_IN_SECONDS - час в секундах - 60*60 = 3600
  • DAY_IN_SECONDS - день в секундах - 60*60*24 = 86400
  • WEEK_IN_SECONDS - неделя в секундах - 60*60*24*7 = 604800
меню

Удаление крон задач

Для удаления крон задач есть 3 функции:

wp_unschedule_event( $timestamp, $hook, $args )

Удаляет конкретную крон-задачу. Для удаления нужно знать все 3 параметра: метку времени, хук, передаваемые параметры.

wp_unschedule_event( 1540722222, 'publish_future_post', array(9227) );

// или так
$timestamp = wp_next_scheduled( 'my_schedule_hook', array(9227) );
wp_unschedule_event( $timestamp, 'my_schedule_hook', array(9227) );

Заметка: если параметр не передается, то его соответственно можно не указывать.

wp_clear_scheduled_hook( $hook, $args )

Удаляет все крон-задачи прикрепленные к указанному хуку и имеющие указанные параметры.

wp_clear_scheduled_hook( 'publish_future_post', array(9227) );
wp_unschedule_hook( $hook ).

Удаляет абсолютно все крон-задачи, прикрепленные к указанному хуку, неважно какая метка времени или какие передаются параметры.

wp_unschedule_hook( 'publish_future_post' );

Наглядно, как работают функции:

меню

Дебаг Крона в WordPress

Крон задачи выполняются асинхронно, поэтому при загрузке страниц сайта вы не увидите ничего, даже если в функции крона вывести что-то на экран, например так die('стоп крон').

Для дебага крон функции нужно напрямую обращаться к файлу http://example.com/wp-cron.php. Или можно оттестировать функцию крона отдельно, и потом просто повесить её на хук крона и убедится что хук сработал в нужное время.

Чтобы увидеть ошибки (если они есть), нужно поставить интервал поменьше (чтобы поймать выполнение нашего задания) и перейти по уже упомянутой ссылке http://example.com/wp-cron.php.

Обращаясь к файлу wp-cron.php напрямую, вы будите насильно инициализировать запуск крона WordPress и увидите, что возвращают ваши функции, если наступило время их выполнения. В том числе можно будет увидеть PHP ошибки, если включена константа WP_DEBUG в wp-config.php.

Может быть полезным временно отключить крон, чтобы никакие крон-задачи не выполнялись на сайте пока вы работаете над функцией крона.

Для отключения крона добавьте в wp-config.php такую константу:

define( 'DISABLE_WP_CRON', true );

Получить весь список текущих крон задач можно функцией _get_cron_array():

print_r( _get_cron_array() );

/*
Array
	[1538467983] => Array
			[wp_update_themes] => Array
					[40cd750bba9870f18aada2478b24840a] => Array
							[schedule] => twicedaily
							[args] => Array()
							[interval] => 43200

			[wp_version_check] => Array
					[40cd750bba9870f18aada2478b24840a] => Array
							[schedule] => twicedaily
							[args] => Array()
							[interval] => 43200

			[wp_update_plugins] => Array
					[40cd750bba9870f18aada2478b24840a] => Array
							[schedule] => twicedaily
							[args] => Array()
							[interval] => 43200

	[1538468836] => Array
			[wp_scheduled_delete] => Array
					[40cd750bba9870f18aada2478b24840a] => Array
							[schedule] => daily
							[args] => Array()
							[interval] => 86400

	[1538469436] => Array
			[wp_scheduled_auto_draft_delete] => Array
					[40cd750bba9870f18aada2478b24840a] => Array
							[schedule] => daily
							[args] => Array()
							[interval] => 86400

	[1538474556] => Array
			[delete_expired_transients] => Array
					[40cd750bba9870f18aada2478b24840a] => Array
							[schedule] => daily
							[args] => Array()
							[interval] => 86400

	[1540722222] => Array
			[publish_future_post] => Array
					[21966ef2cb5e27dd021560702f4ee618] => Array
							[schedule] => 
							[args] => Array
									[0] => 9227

	[1540711111] => Array
			[publish_future_post] => Array
					[21966ef2cb5e27dd021560702f4ee618] => Array
							[schedule] => 
							[args] => Array
									[0] => 9227

	[1540722222] => Array
			[publish_future_post] => Array
					[21966ef2cb5e27dd021560702f4ee618] => Array
							[schedule] => 
							[args] => Array
									[0] => 25

	[version] => 2
*/
меню

Стабильный запуск крона с сервера

В WP, крон запускается нестабильно и нельзя, например, указать точное время запуска. Так получается, потому что для инициализации очередного крон задания нужно, чтобы кто-либо (возможно бот) посетили сайт. Только при посещении сайта происходит проверка и запуск крона если время подошло. Так, если на сайт никто не заходит месяц, то крон не будет работать месяц...

Частично решить эту проблему, можно создав крон задачу на сервере, которая будет выполняться в нужное время или, например, каждые 10 минут. Задача эта должно посещать файл инициализации крона http://example.com/wp-cron.php. Например можно запускать такую команду по крону:

# выполнять команду каждые 5 минут
*/5 * * * * wget -O /dev/null -q 'https://site.ru/wp-cron.php'

# выполнять команду каждый час
* */1 * * * wget -O /dev/null -q 'https://site.ru/wp-cron.php'

В этом случае, крон задачи WP будут срабатывать даже если сайт в нужным момент никто не посещает.

Формат Crontab linux (подробнее читайте здесь):

* * * * * Команда, которая будет выполнена
| | | | |
| | | | └─ День недели (0 - 7) (воскресенье = 0 или 7)
| | | └─── Месяц (1 - 12)
| | └───── День месяца (1 - 31)
| └─────── Час (0 - 23)
└───────── Минута (0 - 59)
меню

Плагины для контроля Крон задач

Полезные ссылки

11 комментов
Полезные 2 Вопросы 3 Все
  • Есть ещё замена Cron:
    Неплохая либа Action Scheduler использовал её для системы авто-аукционов.

    Robust scheduling library for use in WordPress plugins.
    Action Scheduler uses a WordPress custom post type, creatively named scheduled-action.

    4
    Ответить11.Окт.2018 14:16 #
  • Alexandrov Yan39 codyshop.ru

    Тимур подскажи, я правильно понял? Если задача требует длительного времени выполнения, например 20-30 секунд, посетитель этого не заметит, так как страница подгрузится в обычном режиме, а крон-задача будет выполняться фоном?

    Ответить9.Апр.2019 20:34 #
    • Kama7726

      Да, все верно! Однако имей ввиду, что например при включенном плагине query monitor запрос на крон выполняется не фоном (не асинхронно) - это может сбивать с толку...

      2
      Ответить10.Апр.2019 12:58 #
  • Влад

    Подскажите, какой функцией в задаче функции крона можно найти все черновики записей и изменить из статус на "Опуликовано"?

    Ответить28.Авг.2019 20:20 #
  • @ indaled

    А если на сайт никто не заходит, то wp-cron не выполнится никогда? И как следствие его не получится использовать в качестве выполнения импорта ночью по расписанию?

    Как быть в таком случае если заказчик хочет плагин с крон задачами ночью по расписанию?

    Ответить30.Сен.2019 16:06 #
  • @ Мария

    Хочу кроном напоминать админу, о заказах, которые более 3-6 часов висят в статусе новый. Т.е. их долго не обрабатывают.

    @Kama На какой хук лучше повесить создание крон задачи для дополнительного уведомления? Надо чтобы в этот момент система уже знала номер заказа, что в заказе и т.д.

    woocommerce_checkout_order_processed подойдет для этого?

    Спасибо

    И еще вопрос. В каком месте следует использовать -

    wp_clear_scheduled_hook( 'publish_future_post', array(9227) );

    Так получается...??

    add_action( 'wp', 'my_activation' );
    function my_activation() {
    	if ( ! wp_next_scheduled( 'my_five_min_event' ) )
    		wp_clear_scheduled_hook( 'publish_future_post', array(9227) );
    }
    1
    Ответить13.Янв.2020 15:19 #
  • У меня cron не отключается, WordPress все равно шлет запросы на этот файл, хотя я даже сам файл wp_cron.php удалил. Константу в файле wp_config.php тоже прописал.

    Если перейти по IP-адресу, обведенному красной рамкой, то попадаю на свой хостинг.

    Можно ли сделать, чтобы при заходе пользователя на сайт, не отправлялись запросы на wp_cron.php?

    Ответить24.Мар.2020 16:22 #