Загрузка...

WooCommerce HPOS: как адаптировать код плагина под новое хранилище заказов

HPOS

HPOS — это High-Performance Order Storage, высокопроизводительное хранилище заказов в WooCommerce.

Если раньше заказы WooCommerce жили в стандартных таблицах WordPress wp_posts и wp_postmeta, то теперь для них используются отдельные таблицы WooCommerce:

wp_wc_orders
wp_wc_order_addresses
wp_wc_order_operational_data
wp_wc_orders_meta

Задача HPOS простая: меньше таскать заказы через общую помойку wp_posts, быстрее работать с заказами и не превращать базу данных магазина в кастрюлю с макаронами.

Что такое HPOS и зачем он нужен

WordPress изначально создавался не как e-commerce-платформа. Посты, страницы, вложения, товары, заказы — всё долгое время хранилось через одну и ту же модель данных.

Для блога это нормально.

Для магазина с большим количеством заказов — уже не очень.

Старый подход выглядел примерно так:

Заказ → wp_posts
Данные заказа → wp_postmeta

То есть заказ технически был обычным WordPress-постом с типом shop_order.

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

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

Что изменилось для разработчиков плагинов

Главное правило:

нельзя работать с заказами как с обычными WordPress-постами.

Раньше в коде часто встречалось такое:

$post = get_post( $order_id );
$status = get_post_status( $order_id );
$value = get_post_meta( $order_id, '_some_meta_key', true );

update_post_meta( $order_id, '_some_meta_key', 'value' );

При включённом HPOS такой код становится опасным.

Почему?

Потому что актуальные данные заказа могут находиться уже не в wp_posts и wp_postmeta, а в новых таблицах WooCommerce. В результате код может читать старые данные, писать не туда или просто ломать логику.

Правильный путь — использовать WooCommerce CRUD API:

$order = wc_get_order( $order_id );

Дальше работаем уже не с WP_Post, а с объектом WC_Order.

Как адаптировать код под HPOS

Заменяем get_post() на wc_get_order()

Было:

$post = get_post( $order_id );

if ( ! $post || 'shop_order' !== $post->post_type ) {
    return;
}

Стало:

$order = wc_get_order( $order_id );

if ( ! $order ) {
    return;
}

wc_get_order() вернёт объект заказа независимо от того, где сейчас WooCommerce хранит данные: в старых таблицах WordPress или в HPOS.

Заменяем get_post_meta() на get_meta()

Было:

$external_id = get_post_meta( $order_id, '_external_order_id', true );

Стало:

$order = wc_get_order( $order_id );

if ( ! $order ) {
    return null;
}

$external_id = $order->get_meta( '_external_order_id' );

Так код будет корректно работать и со старым хранилищем, и с HPOS.

Заменяем update_post_meta() на update_meta_data()

Было:

update_post_meta( $order_id, '_external_order_id', $external_id );

Стало:

$order = wc_get_order( $order_id );

if ( ! $order ) {
    return;
}

$order->update_meta_data( '_external_order_id', $external_id );
$order->save();

Важно: после изменения метаданных нужно вызвать:

$order->save();

Без save() изменения могут не сохраниться.

Но и дергать save() после каждой мелочи тоже не надо. Если обновляете несколько полей, лучше обновить всё и сохранить заказ один раз:

$order->update_meta_data( '_external_order_id', $external_id );
$order->update_meta_data( '_sync_status', 'success' );
$order->update_meta_data( '_sync_at', time() );

$order->save();

Заменяем delete_post_meta()

Было:

delete_post_meta( $order_id, '_sync_error' );

Стало:

$order = wc_get_order( $order_id );

if ( ! $order ) {
    return;
}

$order->delete_meta_data( '_sync_error' );
$order->save();

Проверяем, является ли ID заказом

Старый вариант:

if ( 'shop_order' !== get_post_type( $order_id ) ) {
    return;
}

Лучше использовать OrderUtil:

use Automattic\WooCommerce\Utilities\OrderUtil;

if ( ! OrderUtil::is_order( $order_id, wc_get_order_types() ) ) {
    return;
}

Или если нужно получить тип заказа:

use Automattic\WooCommerce\Utilities\OrderUtil;

$order_type = OrderUtil::get_order_type( $order_id );

Проверяем, включён ли HPOS

В большинстве случаев это не нужно. Если вы используете wc_get_order() и методы WC_Order, WooCommerce сам разберётся, из каких таблиц читать данные.

Но если у вас есть низкоуровневый код, кастомные SQL-запросы или сложная логика миграции, можно проверить активное хранилище:

use Automattic\WooCommerce\Utilities\OrderUtil;

if ( OrderUtil::custom_orders_table_usage_is_enabled() ) {
    // HPOS включён и используется.
} else {
    // Используется старое хранилище через wp_posts/wp_postmeta.
}

Обновляем запросы заказов

Если раньше вы искали заказы через get_posts():

$orders = get_posts( [
    'post_type'   => 'shop_order',
    'post_status' => 'wc-completed',
    'numberposts' => 10,
] );

лучше перейти на wc_get_orders():

$orders = wc_get_orders( [
    'status' => 'completed',
    'limit'  => 10,
] );

Если нужно искать по метаполю:

$orders = wc_get_orders( [
    'limit'      => 10,
    'meta_query' => [
        [
            'key'   => '_external_order_id',
            'value' => $external_id,
        ],
    ],
] );

Если нужно искать по полям заказа:

$orders = wc_get_orders( [
    'field_query' => [
        [
            'field'   => 'total',
            'value'   => 1000,
            'compare' => '>=',
            'type'    => 'NUMERIC',
        ],
    ],
] );

Для дат тоже есть нормальный вариант:

$orders = wc_get_orders( [
    'date_query' => [
        [
            'column' => 'date_created_gmt',
            'after'  => '1 month ago',
        ],
    ],
] );

То есть не нужно руками писать SQL в wp_posts, если задача решается через API WooCommerce.

Как объявить совместимость плагина с HPOS

После того как код проверен и адаптирован, нужно явно сообщить WooCommerce, что плагин совместим с HPOS.

Добавьте в основной файл плагина:

add_action( 'before_woocommerce_init', function () {
    if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
            'custom_order_tables',
            __FILE__,
            true
        );
    }
} );

Третий аргумент true означает: плагин совместим с HPOS.

Если плагин точно не готов, можно явно объявить несовместимость:

add_action( 'before_woocommerce_init', function () {
    if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) {
        \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
            'custom_order_tables',
            __FILE__,
            false
        );
    }
} );

Но лучше, конечно, не гордо объявлять «я несовместим», а привести код в порядок.

Если декларация не в главном файле плагина

Если код находится не в основном файле плагина, вместо __FILE__ нужно передать путь к главному файлу:

\Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility(
    'custom_order_tables',
    'my-plugin/my-plugin.php',
    true
);

Что проверить в своём коде

Ищите всё, что напрямую работает с заказами как с постами WordPress.

В первую очередь:

get_post()
get_posts()
get_post_meta()
update_post_meta()
delete_post_meta()
get_post_type()
get_post_status()
wp_insert_post()
wp_update_post()
wp_delete_post()
$wpdb
shop_order
wp_posts
wp_postmeta

Само наличие этих функций не всегда ошибка. Например, get_post_meta() может использоваться для обычной записи WordPress, а не для заказа.

Проблема начинается там, где эти функции используются именно для WooCommerce-заказов.

Правильная логика такая:

Работаем с обычными постами WordPress → можно использовать WP API.
Работаем с заказами WooCommerce → используем WC_Order и WooCommerce CRUD API.

Как тестировать HPOS

Минимальный план проверки:

  1. Сделать копию сайта на staging.
  2. Обновить WooCommerce.
  3. Включить HPOS в настройках WooCommerce.
  4. Включить compatibility mode на время проверки.
  5. Прогнать основные сценарии:
    • создание заказа;
    • оплата;
    • смена статуса;
    • возврат;
    • сохранение метаданных;
    • интеграции с доставкой, оплатой, CRM, iiko, 1С и другими внешними системами.
  6. Проверить логи WooCommerce и PHP.
  7. Проверить, нет ли прямых SQL-запросов к заказам через wp_posts и wp_postmeta.

Для WP-CLI сейчас используется пространство команд:

wp wc hpos status

Проверить статус HPOS:

wp wc hpos status

Синхронизировать данные между старым и новым хранилищем:

wp wc hpos sync

Проверить расхождения между хранилищами:

wp wc hpos verify_data

Посмотреть различия по конкретному заказу:

wp wc hpos diff 123

Где 123 — ID заказа.

В старых статьях можно встретить команды вида:

wp wc cot sync

Но в актуальной документации WooCommerce команды HPOS находятся в namespace:

wp wc hpos

Короткий чек-лист адаптации

Если совсем кратко, адаптация под HPOS выглядит так:

1. Не работаем с заказами как с WP_Post.
2. get_post() меняем на wc_get_order().
3. get_post_meta() меняем на $order->get_meta().
4. update_post_meta() меняем на $order->update_meta_data().
5. delete_post_meta() меняем на $order->delete_meta_data().
6. После изменения данных вызываем $order->save().
7. get_posts() по заказам меняем на wc_get_orders().
8. Проверки shop_order переводим на OrderUtil.
9. Прямые SQL-запросы к wp_posts/wp_postmeta убираем или переписываем.
10. Добавляем declare_compatibility( 'custom_order_tables', ..., true ).
11. Проверяем плагин на staging с включённым HPOS.

Вывод

HPOS — это уже не экспериментальная штука «на будущее». Это нормальное хранилище заказов WooCommerce, с которым нужно считаться.

Если плагин работает с заказами через get_post(), get_post_meta() и прямые запросы к wp_posts, его нужно адаптировать.

Хорошая новость: в большинстве случаев всё сводится к простой замене старого WordPress-подхода на WooCommerce CRUD API:

$order = wc_get_order( $order_id );

А дальше работаем с заказом как с объектом WC_Order.

Да, WooCommerce всё ещё местами похож на старый сарай, к которому пристроили второй этаж, балкон и лифт. Но HPOS — как минимум полезная пристройка. И если ваш плагин работает с заказами, лучше привести код в порядок сейчас, чем потом ловить странные баги на живом магазине.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *