<? phpukraine СПІВБЕСІДИ
Пошук по платформі
WORDPRESS · MIDDLE

Що дає WP-CLI і як автоматизувати деплой і рутину WordPress?

WP-CLI завантажує WordPress у CLI-процесі без HTTP, тому дає скриптований доступ до ядра, плагінів і бази: перенесення через search-replace з коректною обробкою serialize(), запуск WP-Cron із системного crontab і власні команди через WP_CLI::add_command з нормальними exit-кодами для CI.

Перенесли сайт на новий домен, після заміни URL злетіли налаштування теми і віджети. Що зробили не так?
Чому заплановані листи на сайті з малим трафіком приходять із запізненням на кілька годин?
Як оновити плагіни на тридцяти сайтах, не заходячи в жодну адмінку?
Навіщо WP-CLI, якщо все це є в адмінці?
WP-CLI деплой search-replace wp-cron автоматизація

WP-CLI завантажує звичайний wp-load.php у CLI-процесі PHP. Ніякого HTTP, ніякого max_execution_time від PHP-FPM, свій memory_limit і при цьому повний доступ до ядра, активних плагінів та всіх хуків. Команда диспетчеризується вже після того, як завантажились mu-plugins, плагіни й тема, тому WooCommerce чи Yoast докидають власні підкоманди, а ваш код може докинути свої. Глобальні прапорці --skip-plugins і --skip-themes дають дістатися до бази навіть тоді, коли фатал у плагіні не пускає в адмінку.

Найчастіше WP-CLI ставлять заради перенесення сайту між доменами, навіть на проєктах без деплой-пайплайна. WordPress зберігає масиви й об'єкти через serialize(), а той пише довжину кожного рядка: s:24:"https://shop.example.com". Заміна домену через sed по дампу або через SQL REPLACE() міняє вміст і не чіпає число, після чого unserialize() повертає false, і налаштування теми, віджети, кастомайзер та метадані білдерів зникають без жодної помилки в логах. wp search-replace розбирає такі значення рекурсивно, підміняє рядок і серіалізує назад. За замовчуванням він ходить тільки по таблицях із $wpdb->tables, тож для таблиць плагінів потрібен --all-tables-with-prefix або --all-tables. --skip-columns=guid обов'язковий: guid для фідів працює як постійний ідентифікатор запису, а не як адреса сторінки. --dry-run спершу покаже, скільки й де зміниться. Прапорець --precise вимикає оптимізацію через SQL REPLACE() і жене кожен рядок через PHP: повільніше, зате коректно там, де в одному полі змішані серіалізовані й звичайні дані.

WP-Cron взагалі не cron. Під час завантаження сторінки WordPress перевіряє чергу і, якщо щось прострочене, робить неблокуючий loopback-запит на wp-cron.php. Звідси й наслідки: сайт без відвідувачів не виконує нічого, за повносторінковим кешем події спрацьовують зрідка, а на staging із базовою HTTP-авторизацією loopback не проходить взагалі. Робоча схема: define('DISABLE_WP_CRON', true) у wp-config.php плюс рядок у crontab, який кожні кілька хвилин викликає wp cron event run --due-now. Поставити crontab і не вимкнути внутрішній механізм гірше, ніж не ставити зовсім: події почнуть запускатись паралельно з двох джерел. Для розбору wp cron test перевіряє прохідність loopback, wp cron event list показує прострочені події з next_run_relative, а wp cron event run <hook> запускає конкретний хук вручну.

Власні команди перетворюють WP-CLI на місце, куди складають усю рутину: перерахунок залишків, реіндексацію, розсилку, разові міграції даних. WP_CLI::add_command('shop resync-stock', [new Shop_CLI(), 'resync']) реєструє метод, а синопсис, валідацію аргументів і вивід wp help WP-CLI будує з докблока за блоком ## OPTIONS. Довгі цикли ганяйте партіями через WP_CLI\Utils\make_progress_bar() і скидайте wp_cache_flush() між партіями, інакше внутрішній кеш об'єктів з'їдає пам'ять на десятках тисяч записів. Для автоматизації найбільше важить те, чим команда завершується: WP_CLI::error() пише в STDERR і виходить із кодом 1, а echo плюс return дає код 0, і пайплайн із set -e вважає провалену міграцію успішною.

Обмеження теж називайте вголос. Транзакційності WP-CLI не дає: якщо search-replace впаде посередині на базі в кілька гігабайт, ви отримаєте наполовину замінені дані, тому перед ним завжди йде wp db export, а сама заміна на живому проді робиться у вікні обслуговування. wp db export і wp db import викликають зовнішні mysqldump і mysql, яких у мінімалістичному контейнері може не бути. Запуск від root під --allow-root залишає файли в wp-content/uploads із власником root, після чого веб-сервер не може їх перезаписати. Файлова частина міграції теж лишається за кадром: wp-config.php, конфіг nginx і захардкоджені URL у темі жоден search-replace не полагодить.

#!/usr/bin/env bash
# Оновлення staging дампом з продакшену. Запускати з кореня WordPress.
set -euo pipefail

OLD='https://shop.example.com'
NEW='https://staging.shop.example.com'
# Ті самі URL, як їх зберігають page builders усередині JSON
OLD_JSON='https:\/\/shop.example.com'
NEW_JSON='https:\/\/staging.shop.example.com'

wp maintenance-mode activate
wp db export "backup/staging-$(date +%F-%H%M).sql" --porcelain
wp db import dump/prod-latest.sql

# Холостий прогін показує кількість замін по кожній таблиці й колонці
wp search-replace "$OLD" "$NEW" --all-tables --skip-columns=guid --dry-run

# Реальна заміна: значення з serialize() розпаковуються і збираються назад
wp search-replace "$OLD" "$NEW" --all-tables --skip-columns=guid --report-changed-only
wp search-replace "$OLD_JSON" "$NEW_JSON" --all-tables --report-changed-only

wp core update-db      # схема бази з prod буває старішою за код staging
wp cache flush         # Redis досі віддає старі опції з домену prod
wp rewrite flush --hard

# Staging не має індексуватись і слати листи покупцям
wp option update blog_public 0
wp plugin deactivate woocommerce-payments mailchimp-for-wp

# WP-Cron вимкнено в wp-config.php, події тягне системний crontab
wp cron event list --fields=hook,next_run_relative,recurrence
wp cron event run --due-now --quiet

wp maintenance-mode deactivate
Що wp search-replace розпаковує serialized-значення й перераховує довжини рядків, тому sed по SQL-дампу ламає option_value і postmeta, а WP-CLI ні.
Що guid змінювати не можна, звідси --skip-columns=guid, а перед реальним прогоном іде --dry-run.
Що WP-Cron запускається loopback-запитом на wp-cron.php під час відвідування сторінки, тому без трафіку події висять; лікується DISABLE_WP_CRON плюс wp cron event run --due-now із системного crontab.
Що власна команда реєструється через WP_CLI::add_command, синопсис і --help WP-CLI бере з докблока методу, а WP_CLI::error завершує процес кодом 1, і саме на це реагує CI.
Що aliases у wp-cli.yml (@staging із ssh:) дозволяють ганяти ту саму команду на кількох середовищах, а --skip-plugins рятує, коли фатал у плагіні не дає завантажити сайт.
Міняти домен через sed або SQL REPLACE() по дампу і потім не розуміти, чому порожні налаштування теми та віджети.
Запускати search-replace без --dry-run і без --skip-columns=guid, переписуючи guid у всіх записів.
Вважати WP-Cron системним кроном і обіцяти, що подія о 03:00 виконається о 03:00.
Додати рядок у crontab, але не поставити DISABLE_WP_CRON, і отримати паралельні запуски тих самих подій.
Реєструвати команду без перевірки defined('WP_CLI') і класти fatal error на фронті.
Писати в команді echo і return замість WP_CLI::log і WP_CLI::error: пайплайн бачить exit code 0 і вважає деплой успішним.
ПОРАДА

Скажіть, що будь-який search-replace починаєте з --dry-run і --skip-columns=guid, а після імпорту бази робите wp cache flush. На проєктах із Redis половина «містичних» багів одразу після переносу пояснюється саме тим, що об'єктний кеш ще віддає старі опції.

оновлено 8 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

serialize() зберігає довжину кожного рядка. Змінили домен на довший або коротший - довжина стала брехнею, unserialize() повертає false, і налаштування теми, віджети та метадані просто зникають. wp search-replace розпаковує такі значення, міняє вміст і серіалізує назад.