跳到主要内容

如何添加操作和过滤器

像许多 WordPress 插件一样,WooCommerce 提供了各种操作和过滤器,允许开发者扩展和修改平台。

在撰写新代码或修改现有代码时,通常希望添加新的钩子,但务必谨慎对待。本文旨在提供关于此方面的总体指导。

我们通常允许、支持和鼓励的做法包括:

另一方面,我们不鼓励以下做法:

除了以上内容,我们通常遵循 WordPress 编码标准。 尤其是在钩子方面,这意味着遵循以下内容:

请注意,我们在这个指南中提供了示例代码,以帮助说明一些原则。 但是,为了保持简洁,我们通常省略不必要的细节,包括文档块(实际上,钩子应该始终附带文档块!)。

优先使用现有的钩子(或其他替代方案)

钩子会带来长期的义务:我们最不希望看到的是,开发者依赖于某个新的钩子,然后我们又将其移除。 然而,这可能会在重构包含钩子的代码时导致困难,有时会延迟有意义的更改,或者限制我们轻松实现更改而不影响向后兼容性的承诺。

因此,在合理的情况下,我们始终优先考虑使用现有的钩子或替代方法,而不是添加新的钩子。

添加生命周期钩子

生命周期钩子可以用于通知某个生命周期事件即将开始,或者已经结束。 这样的事件示例包括:

  • 主要的产品循环
  • 发送电子邮件
  • 渲染模板
  • 产品或订单状态的改变

一般来说,生命周期钩子:

  • 总是成对出现(“before”和“after”)
  • 始终是操作,而不是过滤器
  • “before”钩子通常会提供带有参数数组的回调函数(如果存在)
  • “after”钩子通常也会提供带有函数的返回值的回调函数(如果存在)

请注意,生命周期钩子主要用于允许其他系统观察,而不是用于修改结果。 当然,这并不妨碍函数的作者同时提供一个过滤器钩子,以实现该功能。

例如,我们认为“获取促销信息”的过程是“生命周期事件”,而不是函数本身:

function woocommerce_get_current_promotions( ...$args ) {
/* 任何初始准备,然后第一个生命周期钩子... */
do_action( 'woocommerce_before_get_current_promotions', $args );
/* ...执行实际操作,然后最终生命周期钩子... */
do_action( 'woocommerce_after_get_current_promotions', $result, $args );
/* ...返回结果,可选地通过过滤器... */
return apply_filters( 'woocommerce_get_current_promotions', $result, $args );
}

逃逸钩子

在某些情况下,支持函数的短路可能是有意义的。 我们称之为“逃逸钩子”,这可以作为一种覆盖代码的手段,当没有更好的方法时。

  • 逃逸钩子始终是过滤器
  • 它们应该始终提供 null 作为初始的可过滤值
  • 如果值被更改为非 null 值,则函数应该提前退出,并返回该新值

为了类型安全,应注意确保,如果函数被短路,则返回类型必须与函数文档块中声明的函数签名和/或返回类型匹配。

示例:

function get_product_metrics( $args ): array {
$pre = apply_filters( 'pre_woocommerce_get_product_metrics', null, $args );

if ( $pre !== null ) {
return (array) $pre;
}

/* ...默认逻辑... */
return $metrics;
}

修改函数输入和输出 (全局渲染函数)

对于全局渲染或格式化函数(通常称为“模板标签”),如果难以实现更好的替代方案,可以为函数的参数和函数的返回值添加过滤器。

这应该谨慎使用,并且仅在必要时进行。 请记住,虽然这为其他组件提供了进行广泛自定义的机会,但它也可能导致其他组件出现问题,因为它们期望获得未修改的输出。

示例:

function woocommerce_format_sale_price( ...$args ): string {
/* 准备填充任何缺失的 $args 值... */
$args = (array) apply_filters( 'woocommerce_format_sale_price_args', $args );
/* ...实际工作以确定 $price 字符串... */
return (string) apply_filters( 'woocommerce_format_sale_price', $price, $args );
}

优先传递对象而不是 ID

某些操作或过滤器会提供一个对象 ID(例如,产品 ID),而其他操作或过滤器会提供实际的对象本身(例如,产品对象)。 为了保持一致性,建议传递对象。

示例:

function get_featured_product_for_current_customer( ) {
/* ...用于查找当前客户的特色产品的逻辑... */

return apply_filters(
'woocommerce_featured_product_for_current_customer',
$product, /* WC_Product */
$customer
);
}

将生命周期钩子与执行方法相关联

有时,可能会有多个路径导致相同的操作。 例如,订单可以通过 REST API、管理环境或前端进行更新。 此外,它也可能通过 AJAX 或通过常规请求进行。

但是,重要的是不要将高级过程的钩子与特定的执行路径相关联。 例如,当订单创建时触发的操作,不应该仅在管理环境中的 AJAX 请求中触发。

相反,请优先使用更通用的钩子,该钩子将传递有关执行方法的上下文信息给回调函数。

以下是我们要避免的示例:

/**
* 假想此函数仅在 AJAX 请求后调用
* (它本身可能使用 `wp_ajax_*` 操作进行挂钩)。
*/
function on_ajax_order_creation() {
/* 避免这样做! */
do_action( 'woocommerce_on_order_creation' );
}

使用过滤器作为功能标志

有时,人们可能会尝试使用过滤器作为一种功能标志,以启用或禁用某个功能。 应该避免这样做! 最好使用选项:

  • 选项持久存储在数据库中。
  • 选项已经是可过滤的(非常适合临时覆盖)。

以下是我们要避免的示例:

/* 避免这样做 */
$super_products_enabled = (bool) apply_filters( 'woocommerce_super_products_are_enabled', true );

/* 最好这样做 */
$super_products_enabled = get_option( 'woocommerce_super_products_are_enabled', 'no' ) === 'yes';

钩子的位置

过滤器不应放置在仅包含模板的操作中。如果模板中使用的某个值需要进行过滤,则相关的逻辑应移动到负责加载模板的函数或方法中,并将结果作为模板变量传递。

此外,建议不要将过滤器钩子放置在数据存储类中,因为这可能会降低这些组件的完整性:因为,根据设计,它们可以通过自定义实现进行替换,因此意外破坏这些自定义存储的风险更高。

钩子名称中的枚举值

虽然存在动态钩子名称的情况(其中钩子名称的一部分是使用变量创建的),但一个好的经验法则是,如果变量包含可能被认为是枚举值的,则应避免这种情况。

例如,当错误代码成为钩子名称的一部分时。

示例(我们希望避免的情况):

if ( is_wp_error( $result ) ) {
/* 避免这种情况 */
$error_code = $result->get_error_code();
do_action( "woocommerce_foo_bar_{$error_code}_problem", $intermediate_result );

/* 建议这样做 */
do_action( 'woocommerce_foo_bar_problem', $result );
}

避免这种情况的主要原因是,枚举集合中的值越多,开发者需要在他们的代码中包含的过滤器就越多。

摘要

本文档是关于钩子包含和放置的高级指南,而不是一个详尽的列表。有时可能会出现例外,并且可能存在我们遗漏的良好规则和方法:如果您有任何建议或改进的想法,请联系我们!