第 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_Managerregister()方法接受一个对象,调用它的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_Managerregister()方法重命名为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_Managerregister()方法来反映我们的变化。

 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 示例 – 实现:注册部分