<? 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 половина «містичних» багів одразу після переносу пояснюється саме тим, що об'єктний кеш ще віддає старі опції.

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

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