第 7 部分 – WordPress 和面向对象编程:一个 WordPress 示例 – 实施:管理 WordPress Hooks
已发表: 2022-02-04到目前为止,与插件 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_head
操作的Login_Error
类必须实现Hooks
接口的get_actions()
方法。
class Login_Error implements Hooks { public function get_actions() { return array( 'login_head' => array( 'add_errors', 10, 1 ), ); } }
Hooks_Manager
的register()
方法接受一个对象,调用它的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 ), ); } }
我们将Hooks_Manager
的register()
方法重命名为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) ,即 SOLID 中的“I”,规定:
“任何客户都不应该被迫依赖它不使用的方法。”
这意味着我们现在正在做的正是我们不应该做的。
接口隔离
我们可以通过将接口拆分成更小、更具体的接口来解决这个问题,这样我们的类只需要知道它们感兴趣的方法。
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 ), ); } }
现在我们已经分离了我们的接口,我们只需要更新Hooks_Manager
的register()
方法来反映我们的变化。
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 和面向对象编程:A WordPress 示例 – 定义范围
- 第 4 部分 – WordPress 和面向对象编程:一个 WordPress 示例 – 设计
- 第 5 部分 – WordPress 和面向对象编程:一个 WordPress 示例 – 实现:管理菜单
- 第 6 部分 – WordPress 和面向对象编程:一个 WordPress 示例 – 实现:注册部分