Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Background jobs: Action Scheduler

Long work — sending an order to an accounting system, updating stock, mailing emails — must not run while a customer waits for a page. It is put into a queue. WooCommerce ships with the Action Scheduler library: a job is written to the database and run later by a separate request.

How it works

A job is a hook name plus arguments. You put it into the queue, and Action Scheduler calls do_action() with that name in the background. The job code is an ordinary function attached to the hook.

Where to write the code

Create the includes/jobs.php file and include it in acme-shop.php.

An as-soon-as-possible job

// wp-content/plugins/acme-shop/includes/jobs.php
add_action('woocommerce_order_status_processing', function (int $orderId) {
    as_enqueue_async_action('acme_export_order', ['order_id' => $orderId], 'acme');
});
 
add_action('acme_export_order', function (int $orderId) {
    $order = wc_get_order($orderId);
 
    if (!$order || $order->get_meta('_acme_exported') === 'yes') {
        return;
    }
 
    $response = wp_remote_post('https://erp.example.com/orders', [
        'headers' => ['Content-Type' => 'application/json'],
        'body' => wp_json_encode(['id' => $orderId, 'total' => $order->get_total()]),
    ]);
 
    if (is_wp_error($response)) {
        throw new Exception($response->get_error_message());
    }
 
    $order->update_meta_data('_acme_exported', 'yes');
    $order->save();
});

The first function fires when an order is paid and only enqueues the job — that is instant. The second runs in the background and sends the order. The arguments from the array are passed to the function in order. The third argument, acme, is the group that makes the plugin's jobs easy to find.

Functions

FunctionWhat it does
as_enqueue_async_action($hook, $args, $group)Run as soon as possible
as_schedule_single_action($time, $hook, $args, $group)Run once at the given time
as_schedule_recurring_action($time, $interval, $hook, $args, $group)Repeat with an interval in seconds
as_has_scheduled_action($hook, $args, $group)Whether such a job is in the queue
as_unschedule_all_actions($hook, $args, $group)Cancel jobs

A delayed job

// wp-content/plugins/acme-shop/includes/jobs.php
add_action('woocommerce_order_status_completed', function (int $orderId) {
    as_schedule_single_action(time() + 7 * DAY_IN_SECONDS, 'acme_ask_review', ['order_id' => $orderId], 'acme');
});
 
add_action('acme_ask_review', function (int $orderId) {
    $order = wc_get_order($orderId);
 
    if ($order) {
        wp_mail($order->get_billing_email(), 'How was your purchase?', 'Please leave a review of the product on the site.');
    }
});

A week after the order is completed the customer receives an email asking for a review.

A recurring job

// wp-content/plugins/acme-shop/includes/jobs.php
add_action('init', function () {
    if (!as_has_scheduled_action('acme_sync_stock', [], 'acme')) {
        as_schedule_recurring_action(time(), HOUR_IN_SECONDS, 'acme_sync_stock', [], 'acme');
    }
});
 
add_action('acme_sync_stock', function () {
    // request stock from the external system and update products
});

The as_has_scheduled_action() check prevents creating a new job on every page load. The sync runs once an hour.

The queue in the admin panel

Open WooCommerce → Status → Scheduled Actions. The list shows jobs with the Pending, Complete and Failed statuses, and you can type acme_export_order into the search. A pending job is run by hand with the Run link under its name. If the function threw an exception, the job lands in Failed and the error text is visible in the log column.

Running from the console

By default the queue is processed while someone visits the site. On a production server it is run by the system scheduler:

wp action-scheduler run --group=acme

The command runs all due jobs of the group and prints their number.

Common mistake

A job is not designed to run twice. The network dropped after sending, the job was run by hand once more — and the order went to the accounting system twice. Before acting, check whether it has already been done: in the example the _acme_exported mark serves that purpose.

Thanks for reading!

Donate to the site — we work hard for you.