Что такое WordPress хуки и как их использовать?

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

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

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

Хуки — это Действия (Actions) и Фильтры (Filters)

Действия (Actions) разрешают вам добавить данные или изменить поведение WordPress. Действия будут запускаться в определенный момент внутри WordPress ядра, плагинов или тем. Колбэки (Callback functions) для действий могут выполнять разные задачи, такие как пользовательский вывод или добавление чего-нибудь в базу данных. Колбэки для действий ничего не возращают назад при добавлении на хук.

Документация для WordPress разработчиков

Фильтры (Filters) дают возможность изменить данные в процессе выполнение WordPress ядра, плагинов или тем. Колбэки (Callback functions) для фильтров могут принимать переменные, изменять и возращать их. Все они работают в отдельности друг от друга и не должны создавать никаких побочных действий, такие как изменение глобальных переменных или вывод. Фильтры обязательно должны вернуть что-нибудь назад.

Документация для WordPress разработчиков

В двух словах, действия запускают какой-нибудь код, а фильтры — изменяют переменные. Давайте рассмотрим пару примеров:

add_action( 'save_post', 'notify_administrator', 10, 3 );

function notify_administrator( $post_id, WP_Post $post, $update ) {
	// Really important code here ... 

	wp_mail( $admin_email, $subject, $message );
}

add_filter( 'post_class', 'modify_post_classes', 10, 3 );

function modify_post_classes( $classes, $class, $post_id ) {
	if ( in_array( get_post_type( $post_id ), [ 'review', 'service' ] ) ) {
		$classes[] = 'dark-theme';
	}

	return $classes;
}

Обратите внимание, что колбэк для фильтра (modify_post_classes) обязательно должен вернуть данные, немного измененные или похожие на те, которые он принимает первым аргументом ($classes).

Как технически работают хуки?

Пользователь отправляет какой-то HTTP запрос и интерпретатор PHP запускает нужные WordPress файлы, начиная с ядра, одним из файлов ядра является хранилище для хуков — WP_Hook класс. Затем PHP интерпретатор выполняет код строка за строкой и когда находит функции для добавления колбэков на хуки записывает их в хранилище. Очень важно, что колбэки не запускаются, а записываются в хранилище. Затем, интерпретатор находит функцию для запуска хука, смотрит в хранилище и запускает добавленные колбэк фукнции одна за одной по очереди.

Такой крутой механизм дает нам сумашедшую расширяемость для разработки. Вы разработчик плагина или темы? Вы можете легко изменить поведение ядра, любой другого плагина, или темы без особых проблем (если вы знаете как использовать хуки :)).

Вот, упрощенный пример:

// Запуск кода...
// Загружаем 1-й плагин
add_action( 'before_magic', 'say_hi' ); // Записываем в хранилище

// Загружаем 2-й плагин
add_action( 'before_magic', 'introduce_yourself' ); // Записываем в хранилище

// Загрузили все плагины
// Загружаем активную тему
add_action( 'after_magic', 'applause' ); // Записываем в хранилище

// Загрузили тему
function magic() {
	do_action( 'before_magic' ); // Запускаем все колбэк-фукнкции из хранилища, которые относятся к этому действию. Но после запуска самой фукнции (не сейчас, чуть позже)

	$this->take_hat();
	$this->stick_your_hand_in_your_hat();
	$this->get_hare();

	do_action( 'after_magic' ); // Запускаем все колбэк-фукнкции из хранилища, которые относятся к этому действию. Но после запуска самой фукнции (не сейчас, чуть позже)
}

// Несколько миллисекунд спустя...
// Запускаем какую-то страницу в которой находится код шорткода например:
magic(); // Запускатся функция magic, и в ней уже выполняются все колбэк-фукнции которые вы добавили на хуки before_magic и after_magic.

Таким образом 1-й и 2-й плагины и тема могу взаимодействовать и влиять на код, который находится на какой-то странице или скрыт где-то на бэкенде. Правда круто?

Список фукнций, относящихся к хукам

Все API хуков основано на несколько фукнциях, которые упрощают работу с классом WP_Hook. Мы уже знаем, что хуки разделяются на действия и фильтры, но так же есть ряд функций которые мы можем также разбить на группы:

Фукнции запускающие колбэки, которые были добавлены на хук (найболее используемые):

  • do_action
  • apply_filters

Фукнции запускающие колбэки, которые были добавлены на хук с несколькими аргументами, которые переданные по ссылкеp:

  • do_action_ref_array
  • apply_filters_ref_array

Устаревший фукнции для запуска колбэков. Все еще работают, но выдают warning:

  • do_action_deprecated
  • apply_filters_deprecated

Добавить колбэк на хук (найболее используемые):

  • add_action
  • add_filter

Удалить колбэк с хука:

  • remove_action
  • remove_filter

Удалить все колбэки с хука:

  • remove_all_actions
  • remove_all_filters

Получить кол-во раз, которые этот хук был вызван за текущий запрос:

  • did_action

Проверить, добавлен ли колбэк на текущий хук:

  • has_action
  • has_filter

Получить имя текущего хука:

  • current_action
  • current_filter

Обрабатывается ли в данный момент хук:

  • doing_action
  • doing_filter

Что же такое колбэк?

Я думаю в ходе статьи вы уже устали от слова колбэк. Давайте разберемся, что это такое и почему это так важно для хуков? Итак, Callbale — это псевдотип в PHP. Так как нам нужно запускать наш код не сразу, а в какой-то определенный момент, мы должны дать всю нужную информцию о том, что конкретно нам нужно выполнить. Строку add_action( 'save_post', 'notify_administrator' ); мы должны читать как «на действие сохранения поста вызвать функцию оповещения администратора». Еще раз повторю, потому что это очень важно, мы ничего не вызываем, мы отвечаем на вопросы «Когда?» и «Что нужно запустить?».

В двух словах тип callable — это то, что мы можем запустить. И что же мы можем запустить в PHP? Мы можем запустить функции или публичные методы объекта или класса, но как из этого сделать callback? Просто передать названия метода или функции. Очень сильно рекомендую вам выучить и разобраться в синтаксисе, потому что разработчики WordPress работает с колбэками каждый день:

function do_something_awesome() {
	// ...
}

/**
 * 1. Имя функции.
 */
is_callable( 'do_something_awesome' ); // true

class Awesome {

	public static function do_something() {
		// ...
	}

	public function do_something_more() {
	}

	/**
	 * 4. Методы внутри другого метода.
	 */
	public function hooks() {

		is_callable( [ $this, 'do_something' ] ); // true
		is_callable( [ $this, 'do_something_more' ] ); // true
		is_callable( [ __CLASS__, 'do_something' ] ); // true
		is_callable( 'self::do_something_more' ); // true
	}

	public function __invoke( ...$values ) {
	}
}

$awesome = new Awesome();
$awesome->hooks();

/**
 * 2. Имя статического метода.
 */
is_callable( 'Awesome::do_something' ); // true
is_callable( [ 'Awesome', 'do_something' ] ) ; // true
is_callable( [ Awesome::class, 'do_something' ] ); // true
is_callable( [ $awesome, 'do_something' ] ); // true

/**
 * 3. Имя метода объекта $awesome.
 */
is_callable( [ $awesome, 'do_something_more' ] ); // true

/**
 * 4. Если класс имеет __invoke метод, значит мы можем вызвать класс, как функцию - значит сам объект является колбэком.
 */
is_callable( $awesome ); // true

/**
 * 5. Анонимная функция.
 */
is_callable( function () { // true

} );

Посмотрели код? Теперь вернитесь и посмотрите еще раз. Советую выучить синтаксис и уметь понимать его автоматически при чтении кода, потому что колбэки — это основной механизм хуков и еще многих других вещей в WordPress.

Настройка PhpStorm для хуков

Настройка PHP интерпретатора и добавление путей проекта

PHP Настройки для WordPress хуков в PhpStorm
PHP Настройки для WordPress хуков в PhpStorm
  1. В меню Настройки, перейдите в PHP.
  2. Выберите PHP интерпретатор.
  3. В Включающие пути поле добавте путь, с установкой WordPress. Эта папка должна включать в себя wp-admin и wp-includes подпапки.

Настройка WordPress как фреймворка

Настройки WordPress как фреймворка в PhpStorm
Настройки WordPress как фреймворка в PhpStorm
  1. В меню Настройки, перейдите в PHP | Frameworks.
  2. На странице найдите WordPress и отметьте чекбокс Включить интеграцию с WordPress.
  3. В поле Путь установки WordPress укажите путь корня установки WordPress. Это папка, которая содержит wp-admin и wp-includes подпапки.
  4. Нажми Применить чтобы сохранить настройки.

Результат настройки PhpStorm

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

Автодополнение WordPress хуков в PhpStorm.
Автодополнение WordPress хуков в PhpStorm.

И быстрый поиск вызова хуков. Вы можете кликнуть на значок h↑ и найти все места, где данный хук используется. В резульате, вы можете легко перейти в место, где данный хук используется и найти больше инфорации о его использовании или найти близьлежащие хуки, а так же документацию для хуков.

Быстрый поиск WordPress хуков в PhpStorm
Быстрый поиск WordPress хуков в PhpStorm

Как раставить хуки?

Если вы пишите тему или плагин для массового использования, то вы обязательно должны оставить как можно больше хуков. Как это сделать правильно? На самом деле тема довольно сложна, потому что каждый случай уникальный, но я все же попробую:

  1. Не жадничайте и оставьте как можно больше хуков, чем их больше, тем легче добавить что-то в ваш код. Я уверен, что другие разработчики еще не раз вам скажут спасибо 🙂
  2. Добавляйте фильтры для «мистических» чисел, строк, или массивов. Например:
    1. время жизни кеша (мистическое число).
    2. аргументы для получение списка CPT через WP_Query (магический массив)
  3. Добавьте как можно больше дополнительной инфорации в хук. Не скупитесь на данные, которые есть в контексте вашего кода. Поделитесь ими с разработчиками, которые могут использовать ваш код, будьте лапочками.
  4. Разрешите изменять основные данные, которые у вас есть с помощью apply_filters и обозначьте главные действия с помощью do_action.

Давайте рассмотрим на примере просто запроса в API:

class API {
	const URL = 'https://random-data-api.com/api/code/random_code';

	public function get_data() {

		$response = wp_remote_request(
			self::URL,
			[
				'method'  => 'GET',
				'timeout' => 2,
			]
		);

		$body = wp_remote_retrieve_body( $response );

		if ( is_wp_error( $body ) ) {
			return [];
		}

		$body = json_decode( $body, true );

		if ( ! is_array( $body ) || empty( $body ) ) {
			return [];
		}

		return $body;
	}
}
class API {
	const URL = 'https://random-data-api.com/api/code/random_code';

	public function get_data() {

		do_action( 'hooks_api_before_request' );
		$response = wp_remote_request( self::URL, $this->get_request_arguments() );
		$body     = $this->get_response_body( $response );
		do_action( 'hooks_api_after_request', $response );

		return apply_filters( 'hooks_api_get_data', $body );
	}
	
	private function get_response_body( $response ) {
		if ( is_wp_error( $response ) ) {
			return apply_filters( 'hooks_api_fail_request', [], $response );
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( ! $this->is_valid_body( $body ) ) {
			return apply_filters( 'hooks_api_fail_request', [], $response );
		}
		
		return $body;
	}

	private function is_valid_body( $body ) {
		return apply_filters(
			'hooks_api_is_valid_body',
            is_array( $body ) && ! empty( $body ),
			$body
		);
	}

	private function get_request_arguments() {
		return apply_filters(
			'hooks_api_get_request_arguments',
			[
				'method'  => 'GET',
				'timeout' => 2,
			]
		);
	}
}
  • hooks_api_before_request — Разрешаем делать что-нибудь до отправки запроса в API.
  • hooks_api_after_request — Разрешаем делать что-нибудь после отправки запроса в API.
  • hooks_api_get_request_arguments — Расрешаем изменять аргументы запроса, такие как timeout, headers, и т.д.
  • hooks_api_fail_request — Разрешаем изменить неудачный ответ, например мы можем вернуть какие-то данные по умолчанию.
  • hooks_api_is_valid_body — Разрешает проверить валидность ответа из API.
  • hooks_api_get_data — Расрешает изменить результат, который вы получили из API.

И еще один пример, шорткод, который печатает какую-то таблицу:

add_shortcode( 'dummy_table', 'dummy_table' );
function dummy_table( $attributes ) {
	$api        = new API();
	$table_data = $api->get_data();

	echo '<div class="dummy-table-wrapper">';
	echo '<div class="dummy-table">';
	foreach( $table_data as $row ) {
		echo '<div class="dummy-table-row">';
		printf(
			'<div class="dummy-table-column">%s</div><div class="dummy-table-column">%s</div>',
			! empty( $row['npi'] ) ? esc_html( $row['npi'] ) : '',
			! empty( $row['imei'] ) ? esc_html( $row['imei'] ) : ''
		);
		echo '</div>';
	}
	echo '</div>';
	echo '</div>';
}
add_shortcode( 'dummy_table', 'dummy_table' );
function dummy_table( $attributes ) {

	$api             = new API();
	$table_data      = $api->get_data();
	$attributes      = apply_filters( 'dummy_table_attributes', $attributes );
	$table_data      = apply_filters( 'dummy_table_data', $table_data, $attributes );
	$wrapper_classes = apply_filters( 'dummy_table_wrapper_classes', [ 'dummy-table-wrapper' ], $table_data, $attributes );
	$table_classes   = apply_filters( 'dummy_table_classes', [ 'dummy-table' ], $table_data, $attributes );
	$row_classes     = [ 'dummy-table-row' ];

	do_action( 'dummy_table_before', $table_data, $attributes );

	printf(
		'<div class="%s">',
		implode( ' ', array_map( 'sanitize_html_class', $wrapper_classes ) )
	);
	do_action( 'dummy_table_after_wrapper_open', $table_data, $attributes );

	printf(
		'<div class="%s">',
		implode( ' ', array_map( 'sanitize_html_class', $table_classes ) )
	);

	foreach ( $table_data as $row ) {
		$row         = apply_filters( 'dummy_table_row', $row, $table_data, $attributes );
		$row_classes = apply_filters( 'dummy_table_row_classes', $row_classes, $row, $table_data, $attributes );
		printf(
			'<div class="%s">',
			implode( ' ', array_map( 'sanitize_html_class', $row_classes ) )
		);
		do_action( 'dummy_table_row_start', $row, $table_data, $attributes );
		printf(
			'<div class="dummy-table-column">%s</div><div class="dummy-table-column">%s</div>',
			! empty( $row['npi'] ) ? esc_html( $row['npi'] ) : '',
			! empty( $row['imei'] ) ? esc_html( $row['imei'] ) : ''
		);
		do_action( 'dummy_table_row_end', $row, $table_data, $attributes );
		echo '</div>';
	}

	echo '</div>';

	do_action( 'dummy_table_before_wrapper_close', $table_data, $attributes );
	echo '</div>';
	do_action( 'dummy_table_after', $table_data, $attributes );
}
  • dummy_table_attributes, dummy_table_data, dummy_table_row — Разрешаем изменить освновные данные.
  • dummy_table_wrapper_classes, dummy_table_classes, dummy_table_row_classes — Позволяем добавить любые дополнительные классы к основным элементам разметки.
  • dummy_table_before, dummy_table_after_wrapper_open, dummy_table_row_start, dummy_table_row_end, dummy_table_before_wrapper_close, dummy_table_after — Разрешаем изменить контент.

Соглашение об именовании хуков

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

Для примера вы можете использовать Полное название класса (FQCN) + название метода + дополнительная инфорация(опционально). Давайте обновим наш код для запроса в API с помощью этого соглашения:

namespace WPPunkHooks;

class API {
	const URL = 'https://random-data-api.com/api/code/random_code';

	public function get_data() {
		$body = $this->make_request();

		return apply_filters( 'wp_punk_hooks_api_get_data', $body );
	}

	private function make_request() {
		do_action( 'wp_punk_hooks_api_make_request_before' );
		$response = wp_remote_request( self::URL, $this->get_request_arguments() );
		$body     = $this->get_response_body( $response );
		do_action( 'wp_punk_hooks_api_make_request_after', $response );

		return $body;
	}

	private function get_response_body( $response ) {
		if ( is_wp_error( $response ) ) {
			return $this->fail_request( $response );
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( ! $this->is_valid_body( $body ) ) {
			return $this->fail_request( $response );
		}

		return $body;
	}

	private function fail_request( $response ) {
		return apply_filters( 'wp_punk_hooks_api_fail_request', [], $response );
	}

	private function is_valid_body( $body ) {
		return apply_filters(
			'wp_punk_hooks_api_is_valid_body',
			is_array( $body ) && ! empty( $body ),
			$body
		);
	}

	private function get_request_arguments() {
		return apply_filters(
			'wp_punk_hooks_api_get_request_arguments',
			[
				'method'  => 'GET',
				'timeout' => 2,
			]
		);
	}
}

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

Так же в документации WordPress есть еще один интересный момент на тему именования хуков — это использовать фигурные скобки для динамических мест и не использовать оператор конкатинации(.):

do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );

Вместо:

do_action( $new_status . '_' . $post->post_type, $post->ID, $post );

Причиной такого соглашения является упрощение работы для инстументов анализа кода, поиска в PhpStorm, инструментах генерации документации и т.д.

На этом у меня все. Удачно похукать 🙂

Источник: What are Hooks in WordPress? How to use WordPress Hooks?

Источник: https://www.kobzarev.com/wordpress/what-are-hooks-in-wordpress-how-to-use-wordpress-hooks/

Михаил Кобзарёв

Суровый русский тимлид. Жил в Магадане, в офисе московских веб студий и в Тульской деревне. Виртуозно знает WordPress, PHP, ООП, Vue.js и вот это вот все. Делает крутые высоконагруженные сайты, поэтому уже почти захватил весь рынок WordPress разработки в России. Не дает никому делать сайты без спроса. Ведет блог о разработке, дайджест в телеграмме и в ВК. Любит сиськи, баню и радиоэлектронику. 100% патриот (но это не точно). Тролль 542 уровня. Ездит в отпуск раз в 5 лет.

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

%d такие блоггеры, как: