利用 WordPress Hooks 的力量:操作和过滤器解释

已发表: 2022-07-22

像任何 CMS 一样,WordPress 并不总是开箱即用地满足您的所有需求。 由于它是开源的,您可以修改它以使其符合您的业务需求——但是,您可以使用 WordPress 的钩子来实现您的目标。 使用钩子构建是一种成功的策略,它可以让 WordPress 开发人员自由地构建几乎任何可以想象的网站功能。

WordPress 钩子:操作和过滤器

WordPress 钩子不仅仅是强大的自定义工具,它们是 WordPress 组件相互交互的方式。 挂钩函数管理我们认为是 WordPress 的一部分的许多日常任务,例如向页面添加样式或脚本,或使用 HTML 元素包围页脚文本。 搜索 WordPress Core 的代码库会发现 700 多个位置的数千个钩子。 WordPress 主题和插件包含更多的钩子。

在我们进入钩子并探索动作钩子和过滤器钩子之间的区别之前,让我们了解它们在 WordPress 架构中的位置。

WordPress 基础设施

WordPress 的模块化元素很容易相互集成,因此我们可以轻松混合、匹配和组合:

  1. WordPress 核心:这些是 WordPress 工作所需的文件。 WordPress Core 提供通用架构、WP 管理仪表板、数据库查询、安全性等。 WordPress Core 是用 PHP 编写的,并使用 MySQL 数据库。
  2. 主题(或父主题):主题定义了网站的基本布局和设计。 由 PHP、HTML、JavaScript 和 CSS 文件提供支持,主题通过读取 WordPress MySQL 数据库来生成在浏览器中呈现的 HTML 代码。 例如,主题中的挂钩可以添加样式表、脚本、字体或自定义帖子类型。
  3. 子主题:我们自己创建子主题,以微调父主题提供的基本布局和设计。 子主题可以定义样式表和脚本来修改继承的功能或添加或删除帖子类型。 子主题说明总是取代父主题的说明。
  4. 插件:为了扩展 WordPress 的后端功能,我们可以从数以千计的第三方插件中进行选择。 例如,插件中的挂钩可以在发布帖子时通过电子邮件通知我们,或者隐藏用户提交的包含禁止语言的评论。
  5. 自定义插件:当第三方插件不能完全满足业务需求时,我们可以通过在 PHP 中编写自定义插件来加速它。 或者我们可以从头开始编写一个新插件。 在这两种情况下,我们都会添加钩子来扩展现有功能。

金字塔从底部到顶部显示五个级别:(1) WordPress 核心,(2) 主题,(3) 子主题,(4) 插件,(5) 自定义插件。
WordPress 基础架构层次结构

既然我们可以访问所有五层的源代码,为什么 WordPress 中需要钩子?

代码安全

为了跟上不断发展的技术,WordPress Core、父主题和插件的贡献者经常发布更新以缓解安全漏洞、修复错误、解决不兼容性或提供新功能。 任何具有应急经验的顾问都知道第一手资料,未能使 WordPress 组件保持最新可能会危及甚至禁用网站。

如果我们直接修改上游 WordPress 组件的本地副本,我们会遇到一个问题:更新会覆盖我们的自定义。 在自定义 WordPress 时我们如何规避这个问题? 通过钩子,在子主题和自定义插件中。

在我们的儿童主题中编码

子主题是一个安全的空间,我们可以在其中自定义已安装主题的外观。 此处添加的任何代码都将覆盖父级中的类似代码,而不会被更新覆盖。

当子主题被激活时,它会链接到已停用的父主题,继承并展示父主题的特征,同时不受父主题更新的影响。 为了不被修改主题的诱惑,最佳实践建议在我们的设置中激活子主题。

编写自定义插件

当一个插件被激活时,它的functions.php文件在服务器上的每个调用中执行。 反过来,WordPress 根据优先级从所有活动插件中加载和排序钩子,并按顺序执行这些钩子。 为了扩展第三方插件的功能,我们可以编写自己的 WordPress 自定义插件。

在 WordPress 中放置我们的钩子的位置

目标例子在哪里?
子主题 PHP 自定义插件 PHP
修改网页结构添加自定义样式表以更改网站元素的颜色和字体
修改另一个插件的功能(即创建一个插件来增强第三方插件的功能) 为自定义帖子类型添加副标题(例如,“新闻”)
添加超越 WordPress Core 的新功能修改访问帖子时发生的工作流程以包括更新数据库中的计数器

潜水前准备:定义

为避免混淆术语,我们将坚持使用以下术语:

  • 钩子是 WordPress 中注册函数以运行的最佳位置。 我们可以将我们的函数连接到 WordPress 及其组件中的众多钩子之一,或者创建我们自己的。
    • 动作挂钩运行动作。
    • 过滤器挂钩运行过滤器。
  • 挂钩函数是我们“挂钩”到 WordPress 挂钩位置的自定义 PHP回调函数。 使用哪种类型取决于钩子是否允许在函数之外进行更改——例如,直接添加到网页输出、修改数据库或发送电子邮件。 这些被称为副作用
    • 过滤器(或过滤器函数)应该避免副作用,只处理传递给它的数据,然后返回修改后的副本。
    • 相反,动作(或动作函数)旨在引起副作用。 它没有返回值。

显示与兼容挂钩配对的功能的图表。过滤器钩子附加了过滤器函数,动作钩子附加了动作函数。
WordPress 钩子可以有多个回调函数,但所有回调函数都必须与它们注册的钩子类型相匹配。

考虑到这些区别,我们可以开始探索钩子了。

抽象和干净的代码

当一个动作或过滤器根据需要合并到一个钩子中时,我们实现了每个任务只编写一个函数并避免在项目中重复代码的目标。 例如,假设我们想将相同的样式表添加到我们主题中的三个页面模板(存档、单页和自定义帖子)。 与其重写父模板中的每个模板,然后在子主题中重新创建每个模板,然后将样式表添加到各个 head 部分,我们可以在单个函数中编写代码并使用wp_head钩子附加它。

周到的命名法

通过唯一地命名子主题或自定义插件挂钩来主动避免冲突。 在单个站点中使用同名挂钩是导致意外代码行为的秘诀。 最佳实践规定我们以唯一的短前缀(例如,作者、项目或公司的首字母)开始我们的钩子名称,然后是描述性的钩子名称。 例如,对于项目 Tahir 的 Fabulous 插件,使用“项目首字母加挂钩名称”模式,我们可以将挂钩tfp-upload-documenttfp-create-post-news

并发开发和调试

一个钩子可能触发不止一个动作或过滤器。 例如,我们可以编写一个包含多个脚本的网页,所有这些脚本都使用wp_head动作钩子在页面前端的<head>部分中打印 HTML(例如, <style><script>部分)。

因此,多个插件开发人员可以在单个插件上并行推进多个目标,或者将插件分成多个更简单的单独插件。 如果某个特性不能正常工作,我们可以直接调查和调试它的钩子函数,而不必搜索整个项目。

行动

当 WordPress 中发生事件时,操作会运行代码。 操作可以执行以下操作:

  • 创建数据。
  • 读取数据。
  • 修改数据。
  • 删除数据。
  • 记录登录用户的权限。
  • 跟踪位置并将它们存储在数据库中。

可以触发操作的事件示例包括:

  • init ,在 WordPress 加载之后但在将标头发送到输出流之前。
  • save_post ,当帖子被保存时。
  • wp_create_nav_menu ,在导航菜单创建成功之后。

动作可以与 API 交互以传输数据(例如,社交媒体上帖子的链接),但它不会将数据返回给调用钩子。

假设我们想通过社交媒体自动分享我们网站上的所有新帖子。 首先查看 WordPress 文档以查找可以在发布帖子时触发的钩子。

找到我们的钩子没有捷径:我们将通过经验学习或仔细研究列出的行动以找到可能的候选人。 我们可能会考虑save_post一个候选者,但很快就会排除它,因为它会在单个编辑会话期间触发多次。 更好的选择是transition_post_status ,它仅在帖子状态更改时触发(例如,从draftpublish ,从publishtrash )。

我们将使用transition_post_status但也会改进我们的操作,使其仅在我们的帖子状态转换为publish时运行。 此外,通过遵循各种社交媒体平台的官方文档和 API,我们可以集成和发布我们的帖子内容以及特色图片:

 <?php function publish_post_on_social_media ( $new_status = NULL, $old_status = NULL, $post_ID = NULL ) { if ( 'publish' == $new_status && 'publish' != $old_status ) { // build the logic to share on social media } } add_action( 'transition_post_status', 'publish_post_on_social_media', 10, 3 ); ?>

既然我们知道了如何使用动作钩子,那么有一个特别有用,尤其是在涉及 CSS 时。

使用wp_enqueue_scripts指定优先级

假设我们想在加载完所有其他样式表后最后添加子主题的样式表,以确保源自其他地方的任何同名类都被我们的子主题的类覆盖。

WordPress 以默认顺序加载样式表:

  1. 家长主题
  2. 儿童主题的
  3. 任何插件

在这个结构中:

 add_action( string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1)

…添加动作的priority值决定了它的执行顺序:

  • wp_enqueue_scripts (或任何操作)的默认priority值为“10”。
  • 如果我们将其priority重置为较低的数字,则函数会更早运行。
  • 如果我们将其priority重置为更高的数字,则函数将稍后运行。

要最后加载我们子主题的样式表,请使用wp_enqueue_scripts ,这是 WordPress 主题和插件常用的操作。 我们只需将子主题的操作wp_enqueue_scripts的优先级更改为高于默认值“10”的数字,例如“99”:

 add_action( 'wp_enqueue_scripts', 'child_theme_styles', 99 );



通常,我们在不寻找返回值时使用操作。 要将数据返回给调用钩子,我们需要查看过滤器。

过滤器

过滤器允许我们在处理数据以在浏览器中显示之前对其进行修改。 为此,过滤器接受变量,修改传递的值,并返回数据以供进一步处理。

WordPress 在为浏览器准备内容之前检查并执行所有注册的过滤器。 这样,我们可以在将数据发送到浏览器或数据库之前根据需要对其进行操作。

我的一位客户通过在产品上印上客户提供的图像来个性化他销售的产品。 此客户端使用 WooCommerce 插件来管理电子商务。 WooCommerce 不支持开箱即用的此功能。 因此,我在客户端的functions.php中添加了两段代码:

  1. WooCommerce 文档中列出的woocommerce_checkout_cart_item_quantity是一个过滤器钩子,允许客户在结帐前将外部元素添加到他们的购物车中。
  2. my_customer_image_data_in_cart是一个过滤器,我们将自己编写并用于在 WooCommerce 准备展示购物车时触发woocommerce_checkout_cart_item_quantity

使用以下模板,我们可以添加过滤器并修改购物车的默认行为:

 add_filter( 'woocommerce_checkout_cart_item_quantity', 'my_customer_image_data_in_cart', 1, 3 ); function my_customer_image_data_in_cart( $html, $cart_item, $cart_item_key ) { if ( !empty( $cart_item['images_data'] ) ) { // Store image // Get image URL // Modify $html } return $html; }

我们添加过滤器的方式与添加操作的方式相同。 过滤器的工作方式与操作类似,包括如何处理优先级。 过滤器和动作之间的主要区别在于,动作不会将数据返回给调用钩子,但过滤器会。

自定义的动作钩子和过滤器钩子

编写自定义操作挂钩不会扩展 Wordpress Core,而只会在我们自己的代码中创建新的触发点。

创建自定义操作挂钩

在我们的主题或插件中添加自定义钩子允许其他开发人员在不修改我们的代码库的情况下扩展功能。 要添加自定义钩子,请使用 WordPress 核心代码库本身使用的相同技术:在我们想要的触发点,我们只需使用新钩子的名称调用do_action ,可以选择添加尽可能多的参数,因为我们的回调可能会觉得有用:

 do_action( 'myorg_hello_action', $arg1, $arg2 );

这段代码简单地运行任何已经挂在我们自定义钩子上的回调函数。 请注意,命名空间是全局的,因此,如前所述,最好在我们的自定义挂钩名称前加上我们组织(也可能是我们的项目)名称的缩写形式,因此这里是myorg_

现在我们已经定义myorg_hello_action ,开发人员可以使用与我们之前介绍的内置钩子完全相同的方式进行挂钩:定义一个函数,然后调用add_action()

除非我们想纯粹在内部使用新的钩子——毕竟这是一种构建代码的有用方式——我们必须通过清晰的文档向下游、我们团队的其他成员或我们插件的外部用户传达它的可用性.

创建自定义过滤器挂钩

WordPress 自定义过滤器钩子的模式与操作钩子的模式相同,只是我们调用apply_filters()而不是do_action()

这次让我们来看一个更具体的例子。 假设我们的插件创建了一个侧边栏菜单,通常由四个项目组成。 我们将添加一个自定义过滤器挂钩,以便我们(和下游开发人员)可以在其他地方修改该项目列表:

 // Text labels of sidebar menu $sidebar_menu = array( "Page One", "Page Two", "Page Three", "Page Four" ); $sidebar_menu = apply_filters( 'myorg_sidebar_menu', $sidebar_menu );

就是这样——我们的自定义过滤器挂钩myorg_sidebar_menu现在可以在插件中使用,该插件可能稍后或在此插件的其他地方加载。 这允许任何人编写下游代码来自定义我们的侧边栏。

在使用内置 WordPress 挂钩时,我们或其他开发人员将遵循相同的模式。 换句话说,我们将从定义一些回调函数开始,这些函数返回它们传递的数据的修改版本:

 function lowercase_sidebar_menu( $menu ) { $menu = array_map( 'strtolower', $menu ); return $menu; } function add_donate_item( $menu ) { $menu = array_push( $menu, 'Donate' ); return $menu; }

与我们之前的示例一样,我们现在准备将过滤器回调函数挂钩到我们的自定义挂钩:

 add_filter( 'myorg_sidebar_menu', 'add_donate_item', 100 ); add_filter( 'myorg_sidebar_menu', 'lowercase_sidebar_menu' );

有了这个,我们已经将我们的两个示例回调函数挂钩到我们的自定义过滤器挂钩上。 现在两者都修改了$the_sidebar_menu的原始内容。 因为我们为add_donate_item赋予了更高的priority值,所以它在lowercase_sidebar_menu执行之后运行。

三个面板描述了本节中描述的过滤器函数的结果。面板 1 显示了侧边栏,因为它没有回调挂钩到过滤器中。面板 2 显示了侧边栏,因为它是连接到过滤器的 lowercase_sidebar_menu 回调,所有四个项目名称都是小写的。面板 3 显示了侧边栏,就像 donate_button 回调也挂接到过滤器中一样——与面板 2 中相同的小写项目加上第五个项目,“捐赠”,留在标题中。

下游开发人员总是可以自由地将更多回调函数挂钩到myorg_sidebar_menu 。 正如他们所做的那样,他们可以使用priority参数使他们的钩子在我们的两个示例回调函数之前、之后或之间运行。

天空是动作和过滤器的极限

借助操作、过滤器和挂钩,WordPress 功能可以突飞猛进。 我们可以为我们的网站开发自定义功能,让我们自己的贡献与 WordPress 一样可扩展。 当我们将 WordPress 网站提升到一个新的水平时,Hooks 让我们坚持安全和最佳实践。

Toptal 工程博客对 Fahad Murtaza 表示感谢,感谢他的专业知识、beta 测试和对本文的技术审查。