WordPress как на ладони
rgbcode is looking for WordPress developers.

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

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

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

Смотрите по теме:

Крон задачи WordPress (из коробки)

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

wp_version_check → wp_version_check()
wp_update_plugins → wp_update_plugins()
wp_update_themes → wp_update_themes()
два раза в день (twicedaily)
Проверка обновлений для плагинов, тем, переводов и ядра.
wp_scheduled_delete → wp_scheduled_delete()
раз в день (daily)
Удаление комментариев и записей из корзины.
delete_expired_transients → delete_expired_transients()
раз в день (daily)
Удаление просроченных временных опций.
wp_scheduled_auto_draft_delete → wp_delete_auto_drafts()
раз в день (daily)
Удаление черновиков (auto-draft).
wp_privacy_delete_old_export_files → wp_privacy_delete_old_export_files()
раз в день (daily) (с WP 4.9.6)
Удаление старых файлов экспорта.
recovery_mode_clean_expired_keys → clean_expired_keys()
раз в день (daily) (с WP 5.2) (не для мультисайта)
Удаление просроченных ключей режима восстановления.
wp_site_health_scheduled_check → WP_Site_Health::wp_cron_scheduled_check()
раз в неделю (weekly) (с WP 5.4)
Проверка и обновление статуса здоровья сайта.
wp_https_detection → wp_update_https_detection_errors()
два раза в день (twicedaily) (c WP 5.7)
Проверка поддерживается ли HTTPS на сайте
do_pings → do_all_enclosures()
Одноразовое событие, создается при создании/обновлении записи со статусом publish.
Обработка пингов для всех записей.
wp_update_user_counts → wp_schedule_update_user_counts().
два раза в день (twicedaily) (c WP 6.0)
Обновляет данные о кол-ве юзеров. Не работает для мультисайта, там работает другая задача update_network_counts.
wp_delete_temp_updater_backups → wp_delete_all_temp_backups()
раз в неделю (weekly) (c WP 6.3).
Удаление содержимого временного каталога /wp-content/upgrade-temp-backup, который используется для создания бэкапа при обновлении WordPress.
wp_update_comment_type_batch → _wp_batch_update_comment_type()
Одноразовая задача (c WP 5.5).
Обновляет тип комментариев. C версии 5.5. дефолтный тип комментария поменялся с '' на 'comment'. Эта задача обновляет все типы комментариев в Базе данных.

Multisite

update_network_counts → wp_update_network_counts()
два раза в день (twicedaily)
Обновляет счетчики для сети мультисайт - это кол-во пользователей и кол-во блогов в сети.

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

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

    if ( ! defined( 'DOING_CRON' ) )
    	add_action( 'init', 'wp_cron' );
  2. wp_cron() регистрирует запуск _wp_cron() на событии wp_loaded.

  3. _wp_cron() проверяет константу DISABLE_WP_CRON, если она отключена, то проверяет существует ли хоть одно задание с подошедшем временем. Если есть, то вызывает функцию spawn_cron().

  4. 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, там эта метка времени используется для разных проверок.

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

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

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

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

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

    Как обрабатываются долгие крон задачи. Допустим, в крон есть несколько заданий. Был отправлен «первый» крон запрос и первое задание начало выполняться. Но выполняется оно долго (больше 60 секунд) в это время был отправлен «второй» крон запрос. В этом случае, второй крон пропустит «долгую» крон задачу (которая сейчас выполняется в первом запросе) и запустит следующую за ней задачу. А первый запрос, после выполнения долгой задачи будет остановлен.

Заметки

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

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

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

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

Вариант 1

Это можно сделать на странице админки «Здоровье сайта». Заходим на страницу и немного ждем (пока ждем WP пробудет сделать «петлевой запрос») если он не удается, мы увидим следующее сообщение и это значит что крон запрос не работает.

В здоровье сайта вы не увидите ошибок, если Cron отключен намерено, через константу DISABLE_WP_CRON.

Вариант 2

  1. Установите плагин WP Crontrol. (После проверки его можно удалить).

  2. Перейдите на страницу плагина События Крон и если крон не работает вы увидите предупреждения о том, что некоторые задания уже просрочены:

Вариант 3

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

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

Причины, по которым WP-Cron может не работать

Сron может не работать по разным причинам:

  • Он отключен намеренно. Установлена константа конфигурации DISABLE_WP_CRON, но не установлен альтернативный вариант запуска.

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

  • Плагин может намеренно или ненамеренно нарушить работу Cron.

  • Фатальная ошибка, вызванная плагином или темой, может нарушить работу Cron.

Больше причин см. здесь

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

По умолчанию крон запускается неблокирующим POST запросом из PHP, с помощью wp_remote_post(). Такой запрос создается с любой посещенной страницы сайта, если время выполнения крон задач подошло.

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

Для этого добавьте такую константу в файл wp-config.php:

define( 'ALTERNATE_WP_CRON', true );

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

Браузер пользователя получит перенаправление, когда необходимо запустить cron. Таким образом, он сразу же возвращается на сайт, в то время как cron продолжит свою работу. Этот метод имеет определенные риски, поскольку зависит от неродного сервиса WordPress.

Подробнее см. spawn_cron().

Запуск WP-cron только с сервера

Чтобы включить запуск крон заданий только с сервера, нужно отключить встроенный запуск крон задач в WP (читайте ниже) и подключить запуск крона с сервера.

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

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

define( 'DISABLE_WP_CRON', true );

Эта опция отключить инициализацию крона в WP. При этом, если отправить прямой запрос на файл /wp-cron.php, то все «созревшие» задачи будут выполнены, как если бы крон работал.

Отключать крон крайне не рекомендуется, потому что через него 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().

Почти всегда функция добавляет новую крон-задачу, даже если она уже существует.

Единственный вариант когда функция не добавит, а обновит данные уже существующей задачи - это когда совпадает метка времени, название хука и параметры добавляемой задачи, т.е. все параметры: $timestamp, $hook, $args.

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

При вызове функции регистрации задачи она записывается в опцию сайта 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( time() + 3600, 'my_new_event', [ $arg1, $arg2, $arg3 ] );

add_action( 'my_new_event', 'do_this_in_an_hour', 10, 3 );

function do_this_in_an_hour( $arg1, $arg2, $arg3 ){
	// делаем что-либо
}

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

Смотрите в описании функции 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
*/

Запуск крона с сервера (UNIX cron)

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

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

# Запуск задачи каждую минуту
* * * * * wget -O /dev/null -q 'https://example.com/wp-cron.php'

# Запуск задачи каждые 15 минут
*/15 * * * * wget -O /dev/null -q 'https://example.com/wp-cron.php'

# выполнять команду каждый час (каждую первую минуту каждого часа)
1 * * * * wget -O /dev/null -q 'https://example.com/wp-cron.php'

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

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

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

Примеры UNIX крона:

# Запуск задачи каждые шесть часов
0 */6 * * * команда

# Запуск задачи ежедневно в 5:30 утра
30 5 * * * команда

# Запуск задачи в первый день каждого месяца
0 0 1 * * команда

# Запуск задачи каждое воскресенье в 23:00
0 23 * * 0 команда

# Запуск задачи с понедельника по пятницу в 18:00
0 18 * * 1-5 команда

# Запуск задачи в понедельник, среду, пятницу, в 15:30
30 15 * * 1,3,5 команда

# Запуск задачи каждый год 1 января в 00:00
0 0 1 1 * команда

Проблема с запуском через php-cli
Файл /wp-cron.php рассчитан на запуск через http. Так если например запускать его через php-cli прямым обращением к нему, то ничего не сработает, как минимум потому что в коде есть обработка переменной $_SERVER['HTTP_HOST'], а её значение php-cli среде скорее всего не то которое должно быть, там обычно будет local-cli.

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

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

41 комментарий
Полезные 3Вопросы 1 Все
    Войти