Жизненный цикл выполнения WordPress представляет собой синхронный конвейер инициализации среды, динамической диспетчеризации хуков и контекстной маршрутизации. В отличие от фреймворков с единым роутером и сервис-контейнером (Laravel, Symfony), WordPress базируется на детерминированной цепочке процедурного подключения файлов ядра (Bootstrapping Sequence), поверх которой развернута событийно-ориентированная шина (Event-Driven Architecture) на основе глобальных реестров $wp_filter и $wp_actions.
В современном стеке WordPress 7.0/7.1+ («Armstrong») ядро претерпело глубокую модернизацию: внедрено безопасное хэширование паролей на базе алгоритма bcrypt (заменившее устаревший phpass), добавлены нативные протоколы совместного редактирования в реальном времени (Gutenberg Phase 3) и унифицированные конвейеры для реактивного фронтенда блоков (Interactivity API) и Block Bindings.
При поступлении входящего HTTP-запроса через точку входа index.php управление немедленно передается загрузчику wp-blog-header.php. Этот скрипт выполняет трехфазный цикл:
wp-load.php → wp-config.php → wp-settings.php): чтение конфигурации, инициализация среды PHP 8.4+, загрузка ранних дроп-инов кэширования, подключение драйвера БД $wpdb, активных плагинов и функций активной темы.wp() → WP::main()): синтаксический анализ запрошенного URI через WP_Rewrite, компиляция SQL-запроса и извлечение данных из MySQL через WP_Query, заполнение глобального контекста $post и $wp_the_query.template-loader.php): выбор наиболее специфичного файла шаблона согласно спецификации Template Hierarchy (для классических тем) или компиляция дерева блоков через Block Engine (для блочных тем FSE) с последующей отдачей HTML-потока в буфер вывода клиента.Архитектуру WordPress критически важно воспринимать не как монолитный блок кода, а как послойный конвейер (Pipeline) с жестко регламентированными контрольными точками.
Bootstrapping Sequence Drop-ins $wp_filter Template Hierarchy Execution Paths[Входящий HTTP-запрос]
│
▼
[1. index.php & wp-blog-header.php] ── Запуск таймера и буфера вывода
│
▼
[2. wp-config.php & wp-settings.php]
├── Pre-DB Drop-ins (advanced-cache.php, sunrise.php)
├── Инициализация $wpdb (db.php) & Object Cache (object-cache.php)
├── WPINC (Классы ядра, функции, l10n, bcrypt-аутентификация)
├── Must-Use Плагины (wp-content/mu-plugins/)
├── Сетевые / Обычные Плагины (active_plugins) ──> do_action('plugins_loaded')
└── Инициализация Темы (functions.php) ──────────> do_action('after_setup_theme')
│
▼
[3. Инициализация Среды: init & wp_loaded]
├── Регистрация CPT, таксономий, эндпоинтов ────> do_action('init')
└── Готовность к обработке входящего запроса ───> do_action('wp_loaded')
│
▼
[4. Диспетчеризация Запроса: WP::main()]
├── WP::parse_request() (WP_Rewrite разбирает URL в query_vars)
├── WP::send_headers() (HTTP-заголовки, Content-Type, Cache-Control)
└── WP_Query::query() (Исполнение SQL, заполнение $wp_query)
│
▼
[5. Рендеринг и Завершение: template-loader.php]
├── do_action('template_redirect') (Шлюз редиректов и fast-exit)
├── Classic Template (single.php) ИЛИ Block Template (FSE block engine)
└── do_action('shutdown') ── Закрытие соединений с БД, сброс FastCGI буфераОбычный HTTP GET-запрос страницы блога или записи. Выполняются все 5 фаз ядра, парсинг URL, SQL-запрос WP_Query и компиляция темы.
Определение констант памяти, загрузка db.php, подключение $wpdb и drop-in объектного кэша object-cache.php.
Нельзя вызывать API функций темы или плагинов — они ещё не загружены.
Сноска: Если кэширующий drop-in advanced-cache.php отдает страницу, ядро останавливается здесь, не подключая БД.
Загрузка wp-config.php, констант безопасности, инициализация соединения $wpdb и подключение drop-ins объектного кэширования.
Если advanced-cache.php отдает закэшированную страницу, ядро завершает выполнение здесь, не подключаясь к MySQL.
Строго последовательная загрузка: MU-плагины, сетевые плагины, активные плагины из БД и functions.php активной темы.
На хуке plugins_loaded сессия пользователя еще не разобрана — wp_get_current_user() вернет гостевого пользователя.
Аутентификация пользователя (bcrypt), регистрация CPT, таксономий, блоков Gutenberg, REST-маршрутов и Block Bindings.
Точка полной готовности бизнес-логики. Доступны права пользователя, но URL страницы еще не распарсен.
WP_Rewrite парсит URL в query_vars, WP_Query выполняет SQL в базе данных, заполняя глобальные $wp_query и $post.
В REST API и AJAX данный шаг подавляется, экономя время выполнения и память PHP.
Определение шаблона по Template Hierarchy или FSE block engine, компиляция HTML и финальный хук shutdown.
Хук shutdown исполняется после закрытия FastCGI-соединения — идеален для фонового логирования и очистки.
Определяются лимиты памяти (WP_MEMORY_LIMIT), ключи безопасности, подключаются drop-ins advanced-cache.php и object-cache.php. Создается глобальный инстанс $wpdb.
База данных доступна, но функции бизнес-логики и хуки плагинов еще не зарегистрированы.
Сначала подключаются Must-Use плагины (mu-plugins), затем обычные активные плагины из базы данных, после чего срабатывает хук plugins_loaded.
Точка для инициализации сервисов и DI-контейнеров плагинов без доступа к авторизации пользователя.
Подключается functions.php активной родительской и дочерней темы. Регистрируется поддержка возможностей темы (add_theme_support).
Функции темы становятся доступными только после завершения этого шага.
Текущий пользователь авторизован ($current_user). Регистрируются Custom Post Types, кастомные таксономии, REST-маршруты и Block Bindings.
Ядро готово принимать любые команды приложения. Завершается глобальный этап wp_loaded.
Парсинг query-переменных, выполнение основного цикла WP_Query, формирование HTTP-заголовков. Срабатывает хук template_redirect.
Финальный рубеж перед выводом: последняя возможность сменить файл шаблона или завершить выполнение через wp_send_json().
Определение шаблона по иерархии, генерация HTML/JSON разметки. По завершении отдачи контента срабатывает хук shutdown.
Хук shutdown вызывается после отправки ответа клиенту; используется для очистки ресурсов и асинхронных задач.
Каждая подсистема WordPress жестко привязана к точке своего появления в конвейере. Попытка запросить контекст до его инициализации приводит к скрытым ошибкам:
wp_get_current_user() вернет ID 0 на хуке plugins_loaded, так как плагины аутентификации и куки сессий расшифровываются только перед хуком init.register_post_type() на after_setup_theme не создаст правила WP_Rewrite, приводя к фатальным ошибкам 404.wp-settings.php вызовет крах скрипта из-за отсутствия экземпляра $wpdb.Drop-in файлы (advanced-cache.php, db.php, object-cache.php) выполняются в глобальной области видимости ДО подключения механизма хуков (add_action() / apply_filters()). Если advanced-cache.php обнаруживает валидный закэшированный HTML в Redis или FastCGI microcache, он отдает ответ клиенту и вызывает exit;. В этом случае PHP завершает выполнение менее чем за 5 мс без единого SQL-запроса к MySQL.
На каком этапе жизненного цикла ядра WordPress гарантированно доступен текущий авторизованный пользователь (wp_get_current_user()), но главный SQL-запрос страницы (WP_Query) еще НЕ был отправлен в базу данных?
advanced-cache.php или template_redirect позволяет отдать закэшированный JSON/HTML за 5–15 мс, минуя тяжелые SQL-запросы WP_Query и рендеринг темы.parse_request или rest_api_init, предотвращая загрузку неиспользуемых ассетов темы и снижая потребление RAM PHP в 4–6 раз.sunrise.php на основе доменного имени запроса.Плагины из каталога wp-content/mu-plugins/ подключаются в алфавитном порядке ДО обычных плагинов базы данных. Помните: для MU-плагинов не вызываются хуки активации и деактивации (register_activation_hook), а ядро не сканирует вложенные директории без кастомного bootstrap-загрузчика.
Жизненный цикл ядра имеет фундаментальную особенность: он разделяется на различные пути исполнения (Execution Paths) в зависимости от характера запроса.
Ниже представлен production-ready инструмент профайлинга на базе современного PHP 8.4+, оформленный как автономный Must-Use плагин. Он фиксирует расход оперативной памяти (RAM) и задержку в микросекундах (microtime) между ключевыми этапами ядра, внедряя диагностический заголовок Server-Timing по стандартам консорциума W3C.
<?php
/**
* Plugin Name: Fullstack Agentic Lab — Core Lifecycle APM Profiler
* Description: Замеряет временные отсечки и потребление RAM на ключевых этапах ядра WordPress.
* Version: 2.1.0
* Author: Artur Yusupov (Fullstack Agentic Lab)
*/
declare(strict_types=1);
namespace AgenticLab\Core\Profiler;
if (!defined('ABSPATH')) {
exit;
}
final class LifecycleProfiler
{
/** @var array<string, array{time: float, memory: int, memory_peak: int}> */
private static array $checkpoints = [];
private static float $startTime;
public static function init(): void
{
self::$startTime = defined('WP_START_TIMESTAMP')
? (float) WP_START_TIMESTAMP
: microtime(true);
self::checkpoint('bootstrap_start');
add_action('plugins_loaded', self::onPluginsLoaded(...), PHP_INT_MIN);
add_action('after_setup_theme', self::onThemeLoaded(...), PHP_INT_MIN);
add_action('init', self::onInit(...), PHP_INT_MIN);
add_action('wp_loaded', self::onWpLoaded(...), PHP_INT_MIN);
add_action('template_redirect', self::onTemplateRedirect(...), PHP_INT_MIN);
add_action('shutdown', self::onShutdown(...), PHP_INT_MAX);
}
public static function checkpoint(string $name): void
{
self::$checkpoints[$name] = [
'time' => microtime(true),
'memory' => memory_get_usage(false),
'memory_peak' => memory_get_peak_usage(true),
];
}
public static function onPluginsLoaded(): void { self::checkpoint('plugins_loaded'); }
public static function onThemeLoaded(): void { self::checkpoint('theme_loaded'); }
public static function onInit(): void { self::checkpoint('init'); }
public static function onWpLoaded(): void { self::checkpoint('wp_loaded'); }
public static function onTemplateRedirect(): void { self::checkpoint('template_redirect'); }
public static function onShutdown(): void
{
self::checkpoint('shutdown');
if (headers_sent() || is_admin() || wp_doing_cron() || (defined('REST_REQUEST') && REST_REQUEST)) {
return;
}
$timingMetrics = [];
$previousTime = self::$startTime;
foreach (self::$checkpoints as $phase => $data) {
$durationMs = ($data['time'] - $previousTime) * 1000.0;
$timingMetrics[] = sprintf('%s;dur=%.2f', $phase, $durationMs);
$previousTime = $data['time'];
}
$totalMs = (microtime(true) - self::$startTime) * 1000.0;
$timingMetrics[] = sprintf('total_execution;dur=%.2f', $totalMs);
// Отправка стандартного заголовка Server-Timing W3C
header('Server-Timing: ' . implode(', ', $timingMetrics));
header(sprintf('X-Peak-Memory: %.2f MB', memory_get_peak_usage(true) / 1048576));
}
}
LifecycleProfiler::init();Заголовок Server-Timing передает метрики фаз ядра прямо во вкладку Network / Timing браузера DevTools. Это позволяет Senior-инженеру в реальном времени мониторить задержки plugins_loaded и template_redirect на production-трафике без установки тяжелых сторонних APM-агентов.
Каким образом архитектурно правильно реализовать отдачу данных из Redis в обход рендеринга темы и тяжелого SQL-запроса WP_Query?
В чем суть ошибки: Вызов функций авторизации (wp_get_current_user(), is_user_logged_in(), current_user_can()) непосредственно в теле плагина или внутри хука plugins_loaded.
Почему разработчики так делают: Стремление заблокировать доступ к плагину или закрытому разделу как можно раньше для экономии серверных ресурсов.
Что происходит в продакшене: На этапе plugins_loaded плагины аутентификации и куки сессий пользователя еще не верифицированы. Функция wp_get_current_user() возвращает нулевой объект WP_User(0), идентифицируя авторизованного администратора как неавторизованного гостя.
Как правильно: Выполнять проверки авторизации строго на хуке init (когда ядро завершило инициализацию пользователя) или на template_redirect (перед отдачей представления):
add_action('template_redirect', function(): void {
if (is_page('dashboard') && !is_user_logged_in()) {
wp_safe_redirect(wp_login_url(get_permalink()));
exit;
}
});В чем суть ошибки: Вызов функции register_post_type() на хуке plugins_loaded или after_setup_theme.
Почему разработчики так делают: Желание объявить структуры данных как можно ближе к файлу конфигурации плагина.
Что происходит в продакшене: Перезапись правил маршрутизации (Rewrite Rules), таксономии и метабоксы на данном этапе еще не зарегистрированы. CPT регистрируется частично, выдавая ошибку 404 при переходе по прямым ссылкам или сбои в редакторе Gutenberg.
Как правильно: Регистрировать CPT строго на хуке init:
add_action('init', function(): void {
register_post_type('project', [
'public' => true,
'has_archive' => true,
'show_in_rest' => true, // Обязательно для Gutenberg и REST API
'supports' => ['title', 'editor', 'custom-fields'],
]);
});В чем суть ошибки: Вызов query_posts($args) внутри шаблона (index.php, archive.php) для кастомной фильтрации или пагинации.
Почему разработчики так делают: Кажется самым простым способом изменить вывод записей без погружения в хуки ядра.
Что происходит в продакшене: Функция query_posts() уничтожает глобальный экземпляр $wp_query и выполняет повторный тяжелый SQL-запрос к MySQL, удваивая задержку TTFB и безвозвратно ломая каноническую пагинацию (/page/2/ выдает 404 Not Found).
Как правильно: Модифицировать параметры главного запроса до его отправки в базу данных через фильтр-действие pre_get_posts:
add_action('pre_get_posts', function(\WP_Query $query): void {
if (!is_admin() && $query->is_main_query() && $query->is_post_type_archive('project')) {
$query->set('posts_per_page', 12);
$query->set('orderby', 'date');
$query->set('order', 'DESC');
}
});В чем суть ошибки: Размещение обращений к базе данных (get_posts(), wp_remote_get(), чтение тяжелых опций) напрямую в глобальной области видимости functions.php.
Почему разработчики так делают: Непонимание того, что файл functions.php компилируется при каждом запросе к сайту, включая cron, rest api, cli и запросы к статическим ассетам.
Что происходит в продакшене: Любой запрос (даже к wp-json/) инициирует неоптимизированные синхронные блокировки внешними API, вызывая просадку RPS сайта в десятки раз.
Как правильно: Инкапсулировать логику в методы классов и привязывать их выполнение к строго соответствующим хукам:
add_action('wp_enqueue_scripts', function(): void {
wp_enqueue_style('theme-styles', get_stylesheet_uri(), [], '1.0.0');
});Боты и автоматические сканеры уязвимостей создают до 80% запросов к несуществующим адресам (.env, wp-login.php, xmlrpc.php). Обработка этих запросов стандартным конвейером с рендерингом темы 404.php способна перегрузить MySQL и исчерпать воркеры PHP-FPM. Отсечение таких запросов через Fast 404 на этапе plugins_loaded или через Nginx/Cloudflare освобождает воркер PHP-FPM за 5–8 мс.
В WordPress 7.0/7.1 ядро автоматически проверяет префикс сохраненного пароля в wp_users. Если хэш начинается с устаревшего $P$ (phpass MD5), при первом входе функция wp_authenticate_username_password() пересчитывает его в $2y$ (bcrypt, cost 12) и обновляет запись в базе данных без необходимости ручного сброса пароля пользователем.