当使用一些 Hooks时,比如 save_post
,我们可能会注意到挂载到这些 Hooks 上的函数可能会被触发多次,例如当我们使用过时的元数据盒子而不是古腾堡插件侧边栏时,就可能会发生这种情况。
关于怎么避免在save_post
Hook上重复执行的问题,可以参考本站之前关于在WordPress 中使用钩子进行主题开发时避免死循环的文章。另外,如果我们使用区块编辑器的方式来创建元数据盒子,这个问题应该就不存在了。
重复Hooks 带来的性能问题
除此之外,还有一种重复执行Hooks 的情况是值得我们讨论的,那就是WooCommerce中的 woocommerce_update_order
Hook,当我们创建或更新 WooCommerce 订单时,woocommerce_update_order
动作 Hook 会被多次触发,这可能会导致性能问题。我们来看一下下面的代码。
add_action( 'woocommerce_update_order', function( $order_id ) {
$email = ''; // 在这里设置你的测试电子邮件地址
wp_mail( $email, '测试邮件', sprintf( '订单 %d 已更新', $order_id ) );
} );
添加了这段代码之后,再创建或更新一个订单,很快我们就会发现,电子邮箱被代码中的测试邮件淹没了。这些邮件就是我们更新订单状态时收到的电子邮件,每一封就代表着一次 woocommerce_update_order
Hook 被触发。
我们知道,发送电子邮件是一个比较耗时的操作,特别是网站服务器到SMTP服务器之间的网络连接比较慢时。如果主题或插件中包含了类似上面的测试代码而没有做任何优化时,我们会发现,下单或更新订单流程会变得非常慢。下面我们来看一下怎么来优化这个问题。
方法一:在挂载到该Hook上面的函数内部移除该Hook
和之前提到的防止死循环的方法类似,我们直接在挂载到 woocommerce_update_order
Hook 的函数中移除该Hook,就可以防止该Hook多次运行。
add_action( 'woocommerce_update_order', 'wprs_order_update', 25, 2 );
function wprs_order_update( $order_id, $order ) {
remove_action( 'woocommerce_update_order', __FUNCTION__, 25, 2 );
// 这个函数在当前请求中不会再次运行
}
这种方法可以很简单地解决问题,但是会有一个潜在影响,看一下下面的情况,假设woocommerce_update_order
运行了5次 :
woocommerce_update_order
– 在这里,我们运行挂载的函数然后断开连接woocommerce_update_order
,woocommerce_update_order
,woocommerce_update_order
– 在这里, WooCommerce 可能做了一些需要在我们的函数中跟踪订单的重要操作,但是我们无法做到这一点,因为我们已经在第一个 Hook 中断开了连接!woocommerce_update_order
如果你觉得这种问题不会对你造成影响,就可以直接使用这种方法来进行优化。如果你想尽量避免这个问题,可以参考第二种方法。
方法二:使用计划任务来运行函数
如果我们阅读过一些 WooCommerce 源代码,就会发现一些插件已经使用了这种方法。所以在WooCommerce 添加类似「woocommerce_update_order_once
」 这种只运行一次的Hook之前,这看起来是解决重复执行问题的一种比较好的办法,下面是该办法的实现代码。
add_action( 'woocommerce_update_order', 'wprs_maybe_order_update' );
function wprs_maybe_order_update( $order_id ) {
as_schedule_single_action(
time() + 5, // 何时运行?5 秒后
'wprs_update_order', // Hook 名称
array( $order_id ), // 传递给 Hook 和函数的参数
'', // 组名称,可以是任何字符串
true // 应该是唯一的吗?- 是的
);
}
add_action( 'wprs_update_order', function( $order_id ) {
$order = wc_get_order( $order_id );
// 在这里进行我们的操作
} );
在上面的代码中:
- 我选择使用
as_schedule_single_action()
创建一个计划任务,也可以考虑使用as_enqueue_async_action()
- 关键点在
as_schedule_single_action
的第五个参数$unique
,我们将其设为true,即可防止为同一个订单创建多个计划任务。