Часть 7 — WordPress и объектно-ориентированное программирование: пример WordPress — реализация: управление хуками WordPress
Опубликовано: 2022-02-04До этого момента взаимодействие с Plugin API означало вызовы add_action()
и add_filters()
в конструкторе каждого класса.
Пока этот подход был достаточно хорош, так как он упрощал вещи и позволял нам сосредоточиться на изучении объектно-ориентированного программирования с помощью WordPress. Однако это не идеально.
Если объект регистрирует все свои крючки при создании, такие вещи, как модульное тестирование, становятся сложными.
ПРИМЕЧАНИЕ. Модульные тесты должны проверять каждый «модуль» отдельно. Даже если вы сейчас не пишете модульные тесты, написание тестируемого кода сэкономит вам много времени на рефакторинг позже, если вы когда-нибудь решите написать тесты.
Менеджер хуков
Давайте сделаем еще один шаг вперед и представим новый класс для управления нашими хуками, мы назовем его Hooks_Manager
. Этот класс будет отвечать за регистрацию всех наших хуков. Итак, мы создадим новый класс с методом register()
.
class Hooks_Manager { /** * Register the hooks of the given object. * * @param object $object */ public function register( $object ) { // Register the hooks the specified object needs } }
Нам нужен интерфейс для каждого класса, который должен регистрировать хуки для реализации.
interface Hooks { /** * Return the actions to register. * * @return array */ public function get_actions(); }
Вы можете думать об интерфейсе как о контракте , где класс, реализующий этот интерфейс, «обязан по контракту» реализовывать все методы, определенные в этом интерфейсе.
Например, класс Login_Error
, который подключается к действию login_head
, должен реализовать метод get_actions()
нашего интерфейса Hooks
.
class Login_Error implements Hooks { public function get_actions() { return array( 'login_head' => array( 'add_errors', 10, 1 ), ); } }
Метод register()
Hooks_Manager
принимает объект, вызывает его get_actions()
и регистрирует все его действия.
public function register( $object ) { $actions = $object->get_actions(); foreach ( $actions as $action_name => $action_details ) { $method = $action_details[0]; $priority = $action_details[1]; $accepted_args = $action_details[2]; add_action( $action_name, array( $object, $method ), $priority, $accepted_args ); } }
Давайте добавим в наш интерфейс метод get_filters()
, чтобы мы могли регистрировать как действия, так и фильтры.
interface Hooks { /** * Return the actions to register. * * @return array */ public function get_actions(); /** * Return the filters to register. * * @return array */ public function get_filters(); }
Вернемся к нашему классу Login_Error
, нам нужно реализовать этот новый get_filters()
.
class Login_Error implements Hooks { public function get_actions() { return array( 'login_head' => array( 'add_errors', 10, 1 ), ); } public function get_filters() { return array( 'authenticate' => array( 'track_credentials', 10, 3 ), 'shake_error_code' => array( 'add_error_code', 10, 1 ), 'login_errors' => array( 'format_error_message', 10, 1 ), ); } }
Мы переименуем метод register()
нашего Hooks_Manager
в register_actions()
. Мы также добавим метод register_filters()
. Эти два метода будут отвечать за регистрацию действий и фильтров соответственно.
class Hooks_Manager { /** * Register the actions of the given object. * * @param object $object */ private function register_actions( $object ) { $actions = $object->get_actions(); foreach ( $actions as $action_name => $action_details ) { $method = $action_details[0]; $priority = $action_details[1]; $accepted_args = $action_details[2]; add_action( $action_name, array( $object, $method ), $priority, $accepted_args ); } } /** * Register the filters of the given object. * * @param object $object */ private function register_filters( $object ) { $filters = $object->get_filters(); foreach ( $filters as $filter_name => $filter_details ) { $method = $filter_details[0]; $priority = $filter_details[1]; $accepted_args = $filter_details[2]; add_filter( $filter_name, array( $object, $method ), $priority, $accepted_args ); } } }
Теперь мы можем снова добавить метод register()
, который будет просто вызывать как register_actions()
, так и register_filters()
.
class Hooks_Manager { /** * Register an object. * * @param object $object */ public function register( $object ) { $this->register_actions( $object ); $this->register_filters( $object ); } // ...
Что, если классу не нужно регистрировать и действия, и фильтры? Интерфейс Hooks
содержит два метода: get_actions()
и get_filters()
. Все классы, реализующие этот интерфейс, будут вынуждены реализовать оба метода.
class Cookie_Login implements Hooks { public function get_actions() { return array( 'auth_cookie_bad_username' => array( 'handle_bad_username', 10, 1 ), 'auth_cookie_bad_hash' => array( 'handle_bad_hash', 10, 1 ), 'auth_cookie_valid' => array( 'handle_valid', 10, 2 ), ); } public function get_filters() { return array(); } }
Например, класс Cookie_Login
должен регистрировать только действия, но теперь он вынужден реализовывать метод get_filters()
только для возврата пустого массива.

Принцип разделения интерфейса (ISP) , «I» в SOLID, гласит:
«Ни один клиент не должен зависеть от методов, которые он не использует».
Это означает, что то, что мы делаем сейчас, это именно то, чего мы не должны делать.
Разделение интерфейса
Мы можем исправить это, разделив наш интерфейс на более мелкие, более конкретные, чтобы наши классы знали только о тех методах, которые их интересуют.
interface Actions { /** * Return the actions to register. * * @return array */ public function get_actions(); }
interface Filters { /** * Return the filters to register. * * @return array */ public function get_filters(); }
Нам больше не нужны и get_actions()
, и get_filters()
, мы можем реализовать только интерфейс Actions
и избавиться от get_filters()
class Cookie_Login implements Actions { public function get_actions() { return array( 'auth_cookie_bad_username' => array( 'handle_bad_username', 10, 1 ), 'auth_cookie_bad_hash' => array( 'handle_bad_hash', 10, 1 ), 'auth_cookie_valid' => array( 'handle_valid', 10, 2 ), ); } }
С другой стороны, Login_Error
, которому нужны действия и фильтры, просто должен реализовать оба интерфейса. Классы могут реализовывать более одного интерфейса, разделяя их запятой.
class Login_Error implements Actions, Filters { public function get_actions() { return array( 'login_head' => array( 'add_errors', 10, 1 ), ); } public function get_filters() { return array( 'authenticate' => array( 'track_credentials', 10, 3 ), 'shake_error_code' => array( 'add_error_code', 10, 1 ), 'login_errors' => array( 'format_error_message', 10, 1 ), ); } }
Теперь, когда мы разделили наш интерфейс, нам просто нужно обновить метод register()
Hooks_Manager
, чтобы отразить наши изменения.
class Hooks_Manager { /** * Register an object. * * @param object $object */ public function register( $object ) { if ( $object instanceof Actions ) { $this->register_actions( $object ); } if ( $object instanceof Filters ) { $this->register_filters( $object ); } } // ...
Таким образом, мы условно вызываем только register_actions()
, только register_filters()
или и то, и другое, в зависимости от интерфейса(ов), которые реализует указанный объект.
Чтобы на самом деле использовать менеджер хуков:
$hooks_manager = new Hooks_Manager(); $hooks_manager->register( $login_error ); $hooks_manager->register( $cookie_login );
Вот и все! Теперь мы можем использовать этот объект для управления хуками во всей кодовой базе.
Вывод
Конечно, есть несколько способов объектно-ориентированного управления хуками, мы только что показали вам один из них. Вы должны поэкспериментировать и найти тот, который соответствует вашим потребностям.
Оставайтесь с нами в последней части этой серии, где мы увидим, как обрабатывать параметры объектно-ориентированным способом, поговорим об инкапсуляции, абстракции и о том, как разделить ваши классы, чтобы создать гибкий плагин, который легко расширять!
Нажмите здесь, чтобы прочитать часть 8 в нашей серии объектно-ориентированного программирования
Смотрите также
- WordPress и объектно-ориентированное программирование — обзор
- Часть 2 — WordPress и объектно-ориентированное программирование: пример из реальной жизни
- Часть 3 — WordPress и объектно-ориентированное программирование: пример WordPress — определение области
- Часть 4 — WordPress и объектно-ориентированное программирование: пример WordPress — дизайн
- Часть 5 — WordPress и объектно-ориентированное программирование: пример WordPress — реализация: меню администрирования
- Часть 6 — WordPress и объектно-ориентированное программирование: пример WordPress — реализация: регистрация разделов