PHP

Action Scheduler для асинхронного выполнения задач в WooCommerce

M
Markabus
·24 марта 2025 г.
Фотореалистичный кадр сверху: на деревянном столе открытый ноутбук с таблицей фоновых задач и цветными статусами, рядом механическая клавиатура, кружка кофе, блокнот с ручкой, витой кабель и внешний SSD — иллюстрация к статье об Action Scheduler в WooCommerce

Покупатель нажимает «Оформить заказ» — и ждёт. А в это время сайт успевает сходить в CRM, отправить письмо, дёрнуть службу доставки и записать что-нибудь в аналитику. Каждый такой вызов добавляет секунды к оформлению, а если внешний сервис не отвечает, покупатель может увидеть ошибку по причине, к его заказу отношения не имеющей.

Решение простое по идее: тяжёлую работу нужно выполнять после того, как покупатель увидел «Спасибо за заказ». Ниже — два способа это сделать: быстрый, на одну строку, и правильный, через очередь задач, которая в WooCommerce уже есть.

Быстрый приём: неблокирующий запрос

Функция wp_remote_post() из WordPress Core API умеет не дожидаться ответа. Достаточно передать параметр 'blocking' => false — PHP отправит запрос и сразу пойдёт дальше, не тратя время на ожидание.

$data = [
    'order_id'      => $order_id,
    'safe_route_id' => $safe_route_id,
    'deal_id'       => $deal_id,
];

$response = wp_remote_post( 'https://crm.example.com/ajax-orders.php', [
    'method'      => 'POST',
    'timeout'     => 45,
    'redirection' => 5,
    'httpversion' => '1.0',
    'blocking'    => false, // Асинхронный запрос!
    'headers'     => [],
    'body'        => $data, // Передаём массив, wp_remote_post сам закодирует
    'cookies'     => [],
] );

// Функция-отправитель НЕ ждёт ответа.
// $response будет содержать объект WP_Error или пустой массив.

Когда этого достаточно и чем приём плох

Способ хорош своей простотой: одна строка в существующем коде — и оформление заказа перестало тормозить. Он уместен, когда результат вам безразличен: пинг в аналитику, необязательное уведомление, «выстрелил и забыл».

Но у него есть цена, и она серьёзная. Вы не узнаете, дошёл ли запрос: ответ не читается, ошибки не видно. Если внешний сервис лежал в этот момент — данные просто потеряны, и никто об этом не сообщит. Нет ни повторной попытки, ни журнала, ни возможности посмотреть, что происходило вчера. Для пинга это приемлемо, для передачи заказа в CRM — нет.

Action Scheduler: очередь задач, встроенная в WooCommerce

Action Scheduler — это библиотека фоновых задач, которая уже установлена вместе с WooCommerce. Отдельно ставить ничего не нужно: если магазин работает, очередь доступна. WooCommerce использует её и для собственных нужд — отложенных операций с заказами, подписками, обновлениями данных.

От wp_cron её отличает то, что задачи хранятся в собственных таблицах базы данных, а не в одной разросшейся опции, и каждая задача имеет статус, аргументы, группу и журнал выполнения. То есть вы всегда видите, что было запланировано, что выполнено, а что упало.

Поставить задачу в очередь

Базовый сценарий — «сделай это как можно скорее, но не сейчас». За него отвечает as_enqueue_async_action():

// 1. Ставим задачу в очередь в момент создания заказа
add_action( 'woocommerce_checkout_order_created', function ( $order ) {
    as_enqueue_async_action(
        'myshop_notify_crm',                  // хук, который выполнится в фоне
        [ 'order_id' => $order->get_id() ],   // аргументы
        'myshop'                              // группа — для фильтрации в админке
    );
} );

// 2. Описываем, что именно делать в фоне
add_action( 'myshop_notify_crm', function ( $order_id ) {
    $order = wc_get_order( $order_id );
    if ( ! $order ) {
        return;
    }

    $response = wp_remote_post( 'https://crm.example.com/api/orders', [
        'timeout' => 30,
        'body'    => [
            'order_id' => $order->get_id(),
            'total'    => $order->get_total(),
            'email'    => $order->get_billing_email(),
        ],
    ] );

    // Здесь ответ уже можно проверить — мы никого не задерживаем
    if ( is_wp_error( $response ) ) {
        throw new Exception( 'CRM недоступна: ' . $response->get_error_message() );
    }
} );

Обратите внимание на две детали. Аргументы передаются в обработчик по порядку значений массива, а не как единый массив. И внутри обработчика уже можно спокойно проверять ответ и бросать исключение: покупатель давно ушёл со страницы, а упавшая задача получит статус «failed» и останется в журнале — вы её увидите.

Отложить на будущее

Если задачу нужно выполнить не сразу, а через какое-то время — например, проверить оплату через пятнадцать минут:

as_schedule_single_action(
    time() + 15 * MINUTE_IN_SECONDS,
    'myshop_check_payment',
    [ 'order_id' => $order_id ],
    'myshop'
);

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

Для регулярной синхронизации остатков подойдёт as_schedule_recurring_action(). Важно не поставить её дважды — иначе получите две параллельные серии задач:

add_action( 'init', function () {
    if ( ! as_has_scheduled_action( 'myshop_sync_stock', [], 'myshop' ) ) {
        as_schedule_recurring_action(
            time(),
            HOUR_IN_SECONDS,
            'myshop_sync_stock',
            [],
            'myshop'
        );
    }
}, 20 );

У всех функций планирования есть параметр $unique — если передать true, повторная постановка той же задачи будет отклонена. Это второй способ защититься от дублей, более надёжный, чем ручная проверка.

Проверить и отменить

Полезный минимум для управления очередью:

// Есть ли такая задача? (быстрая проверка)
as_has_scheduled_action( 'myshop_check_payment', [ 'order_id' => 42 ], 'myshop' );

// Когда выполнится ближайшая
as_next_scheduled_action( 'myshop_check_payment', [ 'order_id' => 42 ], 'myshop' );

// Отменить ближайшую
as_unschedule_action( 'myshop_check_payment', [ 'order_id' => 42 ], 'myshop' );

// Отменить все такие задачи
as_unschedule_all_actions( 'myshop_check_payment', [], 'myshop' );

Одно правило по времени вызова: функции Action Scheduler нельзя дёргать слишком рано. Библиотека готова к работе после хука init, поэтому планируйте задачи внутри init с невысоким приоритетом или позже — как в примере выше.

Где смотреть, что происходит

Главное преимущество очереди перед «выстрелил и забыл» — прозрачность. В админке откройте WooCommerce → Status → Scheduled Actions. Там видны все задачи с фильтром по статусу: ожидающие, выполняющиеся, завершённые, упавшие и отменённые. У каждой можно раскрыть журнал и увидеть, когда она создана, когда начата и с какой ошибкой завершилась.

Это тот случай, когда пять минут на настройку группы ('myshop' в примерах) окупаются: в магазине с активным WooCommerce задач тысячи, и без группы своя задача теряется среди чужих.

Как очередь выполняется на самом деле

Тут скрыт нюанс, из-за которого чаще всего и возникают вопросы «почему задачи не выполняются». Action Scheduler запускается через WP-Cron, а WP-Cron в WordPress зависит от посещаемости: он срабатывает при заходе на сайт. На магазине с трафиком это незаметно, но на тестовом или малопосещаемом сайте задачи будут копиться и выполняться рывками.

Лечится это переводом на системный cron: отключаем встроенный планировщик в wp-config.php и вызываем его по расписанию сервера.

// wp-config.php
define( 'DISABLE_WP_CRON', true );
# crontab, раз в минуту
* * * * * curl -s https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

По документации WooCommerce задачи обрабатываются пачками по 20 штук, и одновременно может выполняться до пяти очередей — это защищает PHP от исчерпания памяти на больших объёмах. Если задач накопились десятки тысяч, разбирать их очередь будет постепенно, а не мгновенно.

Типичные ошибки

Передавать в задачу объекты вместо идентификаторов. Аргументы сохраняются в базу, поэтому кладите туда order_id, а не объект заказа целиком: объект к моменту выполнения устареет, а таблица распухнет.

Рассчитывать на автоматический повтор. Упавшая задача помечается как failed и остаётся лежать — сама она не перезапустится. Если повтор нужен, планируйте его явно из обработчика ошибки.

Забыть про дубли. Постановка повторяющейся задачи при каждой загрузке страницы — классическая причина, по которой через неделю очередь забита тысячами одинаковых записей. Используйте $unique или предварительную проверку.

Не следить за размером таблиц. Завершённые задачи хранятся некоторое время, а затем очищаются автоматически, но на активном магазине таблицы Action Scheduler всё равно становятся заметными. Это стоит учитывать при работе над производительностью — подробнее об этом мы писали в чек-листе оптимизации WordPress.

Что выбрать

Неблокирующий wp_remote_post() — это микрооптимизация для необязательных вызовов, где потеря запроса ничего не стоит. Одна строка, никаких зависимостей, никакого контроля.

Action Scheduler — рабочий инструмент для всего, что действительно должно произойти: передача заказа в CRM, отправка документов, синхронизация остатков, обращения к платёжным сервисам. Он уже стоит в вашем магазине, даёт журнал, отмену, группировку и понятную картину в админке. Если сомневаетесь — берите его: разница в объёме кода составляет несколько строк, а разница в надёжности принципиальная.

Кстати, следить за развитием самой платформы тоже полезно: в разборе WooCommerce 11.0 мы смотрели, что изменилось в последних версиях магазина.

Частые вопросы

Q.Нужно ли устанавливать Action Scheduler отдельно?

Нет, если у вас работает WooCommerce — библиотека поставляется вместе с ним и уже доступна. Отдельная установка нужна только для сайтов на WordPress без WooCommerce.

Q.Чем Action Scheduler лучше неблокирующего wp_remote_post()?

Неблокирующий запрос не даёт никаких гарантий: ответ не читается, ошибка не видна, журнала нет. Action Scheduler хранит задачи в базе, показывает статусы и журнал в админке, позволяет отменять и группировать задачи. Для всего важного нужна очередь.

Q.Почему запланированные задачи не выполняются?

Чаще всего дело в WP-Cron: он срабатывает при заходах на сайт, поэтому на малопосещаемом или тестовом сайте очередь двигается рывками. Решение — отключить встроенный планировщик через DISABLE_WP_CRON и вызывать wp-cron.php системным cron.

Q.Как не создать дубликаты задач?

Передавайте параметр $unique = true при планировании либо проверяйте наличие задачи через as_has_scheduled_action() перед добавлением. Особенно важно для повторяющихся задач, иначе они накапливаются при каждой загрузке страницы.

Источники

Предыдущая
Полезные вспомогательные функции на PHP

Читайте также