WordPress как на ладони

Как загружается ядро WordPress

При работе с темами, плагинами и вообще с любым кодом WordPress, включая хаки в популярном файле темы functions.php. Хорошо бы знать в какой последовательности подключаются php файлы движка, когда срабатывают важные хуки и какие важные константы определены. В этой статье поговорим о такой последовательности загрузки.

Примеры

Чтобы было понятнее, возьмем простую задачу: нам нужно подключить какой-то код в админ-панели WordPress и только там - код не должен срабатывать во фронте или при AJAX запросах. Кто-то скажет, что достаточно сделать проверку с помощью is_admin(), однако это не так, потому что is_admin() вернет true и при AJAX запросе в файл admin-ajax.php.

Или например, можно подключить какую-то логику на хук admin_init и потом долго недоумевать, а почему моя логика работает и при запросах AJAX. Или почему она мешает плагинам, которые работают с AJAX...

Или вот еще пример из комментария. Когда в файле темы functions.php выполняется какой-то код, который зависит от другого кода подключаемого через хук init. Например, вы создали тип записи во время хука init и в самом файле functions.php выполняете проверку, которая опирается на этот новый тип записи и ожидаете что код будет работать, но это не так, потому что прямой код в functions.php срабатывает раньше чем событие init...

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

Порядок загрузки (теория)

Понять логику загрузки не так сложно, как может показаться на первый взгляд. Для этого давайте взглянем на эту, столь любимую мной, картинку:

Порядок загрузки ядра WordPress

Существует 4 варианта загрузки. На самом деле их чуть больше, но это основные:

  • Загрузка Фронтэнда (темы).
  • Загрузка REST запроса.
  • Загрузка Админки.
  • Загрузка AJAX запроса (admin-ajax.php).

Во всех случаях загружается ядро WordPress - файл wp-load.php. Ядро загружается всегда и везде!

Прежде чем переходить к каждому типу загрузки, нужно разобраться как загружается само ядро.

Загрузка ядра WordPress

Ядро WP загружается при любом запросе: фронт, аякс, админка, REST ...

wp-load.php
	wp-config.php
		wp-settings.php

			// Функции загрузки (load): wp_debug_mode(), timer_start(), require_wp_db() ...
			// Функции констант: wp_initial_constants(), wp_cookie_constants() ...
			// Функции плагинов (хуки, активация): do_action(), plugin_dir_url(), register_activation_hook().

			// Устанавливаются константы: WP_START_TIMESTAMP, WP_MEMORY_LIMIT, WP_MAX_MEMORY_LIMIT, WP_DEBUG, SCRIPT_DEBUG, WP_CONTENT_DIR, WP_CACHE.

			// Стандартизируются переменные сервера: wp_fix_server_vars().

			// Проверяется режим разработки: wp_maintenance().

			// Включается таймер (скорость загрузки): timer_start().

			// Включается дебаг режим: wp_debug_mode().

			// Загружается 'wp-content/advanced-cache.php' если он есть и включено WP_CACHE.

			// База данных. $wpdb. require_wp_db().
			// Загружается 'wp-content/db.php' если есть и создается
			// соединение с БД и все связанные переменные (префикс БД...).

			// Объектный кэш: 'wp-content/object-cache.php' если есть, или 'wp-include/cache.php'.

			// Подключаются базовые хуки (фильтры): default-filters.php.

			// Включается Multisite (если нужно)
			// Грузится 'wp-content/sunrise.php' если есть (только для multisite).

			// register_shutdown_function( 'shutdown_action_hook' )

			// SHORTINIT: остановка загрузки, где есть только самое базовое.
			if( SHORTINIT ){
				return false;
			}

			// Подключаются функции локализации.

			// Проверяется установлен ли WP: wp_not_installed().

			// Подключается куча файлов с остальными функциями WordPress.

			// Подключается Must-use плагины и срабатывает событие:
			do_action( 'muplugins_loaded' );

			// Константы cookie, ssl: COOKIEPATH, COOKIE_DOMAIN
			// Общие глобальные переменные: $pagenow, $is_apache, $is_nginx, $is_lynx
			// Глоб. переменные клиента: $is_opera, $is_NS4, $is_safari, $is_chrome, $is_iphone, $is_IE, $is_edge

			// Подключаются Активные плагины.
			// Подключаются pluggable функции: pluggable.php.
			// Срабатывает
			do_action( 'plugins_loaded' );

			// Принудительное экранирование значений в $_POST, $_REQUEST ... см. wp_magic_quotes() .

			// Глобальные переменные:
			// $wp_the_query      — new WP_Query()
			// $wp_query          — $wp_the_query
			// $wp_rewrite        — new WP_Rewrite() — константы, функции и правила ЧПУ.
			// $wp                — new WP() — запрос WP (запускается позже).
			// $wp_widget_factory — new WP_Widget_Factory()
			// $wp_roles          — new WP_Roles()

			// Текущая тема
			do_action( 'setup_theme' );
			// functions.php (child) - сначала подключается functions.php дочерней темы
			// functions.php (parent) - затем подключается functions.php основной темы
			// Файл перевода WordPress: load_default_textdomain()
			do_action( 'after_setup_theme' ); // первый хук доступный в теме

			// Устанавливается текущий юзер (создается объект).
			// См. wp_get_current_user()
			// Юзер часто уже определен после события 'plugins_loaded' плагинами.
			$wp->init();

			// init событие. Когда среда WP, плагины и тема активированны,
			// но на экран еще ничего не выведено:
			do_action( 'init' );

			// Регистрация виджетов: событие 'widget_init'

			// Проверка статуса сайта для мультисайтовой сборки.
			// Сайт может быть: удален, неактивен, в архиве. См. ms_site_check()
			// Если сайт не прошел проверку, то вызывается drop файл и PHP прерывается через die().

			// Тоже самое что `init` только после проверки статуса.
			// До этой строки работа PHP может не дойти. Например при REST запросе.
			do_action( 'wp_loaded' );

При загрузки ядра всегда подключаются файл темы functions.php, даже в админке. Т.е. не важно, нужна нам тема или нет, functions.php работает и поэтому его можно поставить в один ряд с плагинами... Сделано так для удобства, чтобы любой код можно было «сунуть» в наш, потому и любимый, файл functions.php. Такое поведение не логично с точки зрения программирования, но очень удобно с точки зрения разработки.

Ядро, как мы видим, находится в файле wp-settings.php, но перед ним вызывается wp-load.php. Нужно это для того, чтобы найти файл wp-config.php. Дело в том, что wp-config.php может находится в одной папке со всеми файлами wordPress, а может быть вынесен в родительскую папку. Задача wp-load.php отыскать файл конфигурации и подключить его . Если его нигде нет, то WP предложит его создать. Собственно, ровно это происходит при установке WordPress.

После того, как файл конфигурации найден, он подключается. В нем указываются все важные константы, параметры подключения к базе данных и т.д. И затем подключается ядро - файл wp-settings.php.

Загрузка Фронт-энда (темы)

Любые Фронтэнд запросы отправляются в файл index.php в корневой папке домена. Это означает, что WordPress загружается с темой. Для констатации этого факта в index.php определяется константа WP_USE_THEMES. Так например хук template_redirect из файла template-loader.php будет работать только, если определена эта константа.

Процесс загрузки Фронта выглядит так:

index.php
	// определяется константа WP_USE_THEMES

	wp-blog-header.php
		// ЯДРО (описано выше)
		require_once dirname(__FILE__) . '/wp-load.php';

		// Установка основного запроса WordPress.
		// Делает запрос и определяет какая страница загружается.
		// См. https://wp-kama.ru/function/wp
		wp(); // См. WP::main()

		// подключение нужного файла шаблона темы 'template-loader.php'
		require_once ABSPATH . WPINC . '/template-loader.php';
  • Подробнее про установку основного запроса и среду WP, читайте в описании функции wp().

  • Логику работы файла template-loader.php (подключение файла темы) смотрите в: Иерархия шаблона.

Смотрите также картинку о том, как работает запрос WordPress:

Как работают функции запросов в WordPress

Загрузка REST запроса

REST запрос на 90% обрабатывается также как и запрос для фронтенда. Его также начинает обрабатывать файл index.php. Через правила перезаписи (ЧПУ) устанавливается параметр запроса rest_route=маршрут.

^wp-json/?$    => index.php?rest_route=/
^wp-json/(.*)? => index.php?rest_route=/$matches[1]

Процесс загрузки REST запроса выглядит так:

index.php
	// определяется константа WP_USE_THEMES

	wp-blog-header.php
		// ЯДРО (описано тут https://wp-kama.ru/handbook/wordpress/loading#wpcore-load)
		require_once dirname(__FILE__) . '/wp-load.php';

		// в процессе загрузки ядра создается хук `parse_request`
		// который срабатывает в функции `wp()`
		add_action( 'parse_request', 'rest_api_loaded' );

		// Установка основного запроса WordPress.
		// Делает запрос и определяет какая страница загружается.
		// В момент срабатывания хука `parse_request` управление передается
		// функции rest_api_loaded(), которая прерывает PHP через die.
		// См. https://wp-kama.ru/function/wp
		wp(); // См. WP::main()

Пояснения к работе wp() и хуку parse_request

Функция wp() вызывает метод WP::main(), он делает следующие действия по порядку:

  1. Вызывает WP::init().

    Этот метод устанавливает текущего пользователя для обычных (не REST) запросов. см. wp_get_current_user().

    Позднее (если это REST запрос), на хуке аутентификации rest_authentication_errors авторизованный юзер будет обнулен при неправильном nonce коде. Смотрите:

  2. Вызывает WP::parse_request().

    Этот метод устанавливает параметры текущего запроса $wp->query_vars.

  3. Запуск REST — rest_api_loaded().

    В конце WP::parse_request() срабатывает хук parse_request на котором всегда висит функция rest_api_loaded():

    add_action( 'parse_request', 'rest_api_loaded' );

    rest_api_loaded() проверяет параметр запроса $GLOBALS['wp']->query_vars['rest_route']. Если это REST запрос, то запускается логика REST (логика обработки запросов для обычных страниц тут заканчивается).

    1. Устанавливается константа define( 'REST_REQUEST', true ).

    2. Запускается REST сервер rest_get_server().

      При запуске срабатывает хук rest_api_init. На нём в имеющийся сервер добавляются свои маршруты.

    3. Обрабатывается REST запрос WP_REST_Server::serve_request( $route ).

      При обработке запроса проверяется доступность текущего маршрута, маршрут обрабатывается, возвращается ответ REST сервера в виде объекта WP_REST_Response.

      При обработке запроса срабатывают следующие полезные хуки (в указанном порядке):

    4. Работа PHP прерывается через die().

Таким образом

За обработку REST запроса отвечает функция rest_api_loaded().

REST запрос выглядит точно также как обычный фронтэнд запрос. До момента установки основного запроса WordPress функцией wp(). В ней как и в обычном фронтэнд запросе устанавливается текущий юзер. Но:

  • НЕ устанавливаются заголовки WP::send_headers().
  • НЕ происходит основного запроса WP::query_posts().
  • НЕ обрабатывается 404 страница WP::handle_404().
  • НЕ вызывается файл template-loader.php, который определяет шаблон страницы.
  • НЕ работает хук редиректов template_redirect.

Заголовки REST ответа ставит REST сервер, также он делает соответствующий запрос в БД или что-угодно еще.

Загрузка Админки

Админка WordPress никак не связана с «корневым» файлом index.php. Вся её загрузка начинается с файла wp-admin/admin.php.

Первое и главной что там происходит - это определяется константа WP_ADMIN, которая говорит всему коду который выполняется потом, что мы находится в админке.

wp-admin/admin.php
	// определяется константа WP_ADMIN
	define( 'WP_ADMIN', true );

	// ЯДРО (описано выше)
	require_once dirname(dirname(__FILE__)) . '/wp-load.php';

	// проверяется необходимость Апгрейда WordPress

	// подключаются файлы (дополнительные функции) админки
	require_once ABSPATH . 'wp-admin/includes/admin.php';

	// Проверяет авторизован ли пользователь, перед тем как допустить его на любую страницу
	// если прав недостаточно, то юзер «улетит» на страницу авторизации
	auth_redirect();

	// хук админки
	do_action( 'admin_init' );

	// устанавливается данные текущей страницы
	set_current_screen();

	// шапка
	require_once ABSPATH . 'wp-admin/admin-header.php';

	// контент - тут логика разветвляется и контент загружается
	// либо от плагина
	// либо никакой (если это запрос импорта данных)
	// либо родные файлы адмники, например, wp-admin/options-general.php

	// подвал
	include ABSPATH . 'wp-admin/admin-footer.php';

Загрузка AJAX запроса

Отправлять AJAX запросы принято в файл wp-admin/admin-ajax.php, с него и начинается загрузка WordPress при AJAX запросах.

Давайте и тут разберем порядок загрузки, а затем немного пояснений.

wp-admin/admin.php
	// определяется константа WP_ADMIN
	define( 'WP_ADMIN', true );

	// ЯДРО (все что есть в ВП) (описано выше)
	require_once dirname(dirname(__FILE__)) . '/wp-load.php';

	// Проверяется наличие параметра запроса 'action' он должен быть определен для любого AJAX запроса.
	if( empty( $_REQUEST['action'] ) ) die( '0' );

	// подключаются файлы (дополнительные функции) админки
	require_once ABSPATH . 'wp-admin/includes/admin.php';

	// подключаются обработчики Ajax запросов ядра WordPress
	require_once ABSPATH . 'wp-admin/includes/ajax-actions.php';

	// хук админки
	do_action( 'admin_init' );

	// проверка и исправление значений параметра action связанных с ядром WordPress и добавление нужного хука
	add_action( 'wp_ajax_' . $_GET['action'], 'wp_ajax_' . str_replace( '-', '_', $_GET['action'] ), 1 );

	// проверка прав пользователя и запуск нужного хука
	do_action( 'wp_ajax_' . $_REQUEST['action'] );
	// или
	do_action( 'wp_ajax_nopriv_' . $_REQUEST['action'] );

	// Ответ AJAX по умолчанию
	die( '0' );

При AJAX запросе также устанавливается константа WP_ADMIN. Это значит, что любой AJAX запрос выглядит для ядра, как запрос в админ часть, даже если этот запрос делается с фронта... Это не всегда нужно, но так принято в WordPress по причинам стандартизации и удобства использования.

Далее загружается ядро, в котором срабатывают все хуки. Т.е. подключаются и запускаются все плагины, срабатывает код файла functions.php, срабатывает событие init. Т.е. полностью загружается и обрабатывается ядро WordPress.

Теперь, когда ядро обработано, дополнительно подключаются все PHP функции админ-панели (хотя часто они не нужны) и запускается один из AJAX хуков (зависит от авторизации): wp_ajax_(action) или wp_ajax_nopriv_(action).

Запуск AJAX хука это последний этап - AJAX операция выполнена и она должна прервать работу PHP и вывести на экран какие-либо данные - обычно это либо просто строка, либо строка в json формате.

Кстати, для удобного json ответа при AJAX запросах в WP есть специальная функция - wp_json_encode() и две функции производные от нее:

Загрузка CRON запроса

WP с любой страницы отправляет запрос на файл /wp-cron.php. Он, в свою очередь:

  1. "Включает" константу DOING_CRON.
  2. Подгружает среду WordPress (файл /wp-load.php).
  3. Выполняет крон задачи.

Подробнее смотрите здесь.

Занимательная картинка о том, как загружается WordPress

6 комментариев
    Войти