如何进行现代 WordPress 开发(第 2 部分)
已发表: 2022-03-11WordPress 是地球上使用最广泛的网站技术,并且有充分的理由。 然而,遗留代码的核心是一团糟,这个问题会级联到第三方开发人员。 一些开发人员以此为借口在他们自己的 WordPress PHP 代码中偷工减料,但从长远来看,除了最微不足道的更改之外,这种方法的成本更高。
在我们的两部分系列的第 1 部分中,我们专注于整体项目和工作流工具,然后是前端开发。 现在是时候采取行动了,与 PHP 搏斗:具体来说,如何在使用后端 WordPress 代码时遵循最佳实践。 您可能会认为这是一个 PHP/WordPress 教程,但更高级一些,假设您已经完成了一些 WordPress 后端定制。
那么,哪些现代软件设计原则和 PHP 功能将为您的时间带来最大价值? 以下是我强烈推荐的 10 个 WordPress 和 PHP 开发实践。
现代 WordPress 开发最佳实践 #1:遵循“关注点分离”
关注点分离意味着不应将具有不同功能或目的的部分 WordPress PHP 代码混合在一起。 相反,它们应该被组织成不同的部分或模块,通过定义的接口相互传递数据。 (接口是一组定义的参数,模块将其作为输入,以及它输出的内容。)一个密切相关的术语是单一职责原则:每个代码模块(或函数)应该只负责一件事。
遵循这些原则的最终目标是生成模块化的代码,从而可维护、可扩展和可重用。
那是一口难言,所以让我们看一个 WordPress PHP(来自 WordPress 核心)的例子,它把所有东西都纠缠在一起。 这种编码风格通常被称为“意大利面条式代码”,因为几乎不可能理解其内部工作原理。 为简洁起见,以下摘录被删节; 但是,保留了原始样式和格式。
$id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0; <table class="form-table"> <?php $blog_prefix = $wpdb->get_blog_prefix( $id ); $sql = "SELECT * FROM {$blog_prefix}options WHERE option_name NOT LIKE %s AND option_name NOT LIKE %s"; $query = $wpdb->prepare( $sql, $wpdb->esc_like( '_' ) . '%', '%' . $wpdb->esc_like( 'user_roles' ) ); $options = $wpdb->get_results( $query ); foreach ( $options as $option ) { if ( strpos( $option->option_value, "\n" ) === false ) { ?> <tr class="form-field"> <th scope="row"><label for="<?php echo esc_attr( $option->option_name ); ?>"><?php echo esc_html( ucwords( str_replace( '_', ' ', $option->option_name ) ) ); ?></label></th> <?php if ( $is_main_site && in_array( $option->option_name, array( 'siteurl', 'home' ) ) ) { ?> <td><code><?php echo esc_html( $option->option_value ); ?></code></td> <?php } else { ?> <td><input class="<?php echo $class; ?>" name="option[<?php echo esc_attr( $option->option_name ); ?>]" type="text" value="<?php echo esc_attr( $option->option_value ); ?>" size="40" <?php disabled( $disabled ); ?> /></td> <?php } ?> </tr> <?php } } // End foreach </table>
首先,完全无法理解。 我喜欢唯一的评论是End foreach
,这完全是多余的。 我们有数据库查询、查询结果处理、嵌入在 HTML 中的附加处理(如果你没有注意到的话,那里嵌套了一个if
/ else
)、输出转义和 HTML 模板,它们都混合在一起。 另一个问题是直接来自全局$_REQUEST
的$id
参数,而不是将实际参数传递给函数。
看到这里,完全可以理解为什么 WordPress 核心多年来基本保持不变。 重构这种代码——尤其是在保持现有行为的同时——是一项真正的史诗般的任务,没有人愿意做。
那么我们如何正确地做到这一点呢? 好吧,要记住的一件事是,没有一种真正的方法。 我们提到了我们应该争取的上述品质: 我们需要 WordPress 自定义 PHP 代码是可维护的和模块化的。 让我们看看如何将上面的代码拆分为模块。
- 显然,SQL 查询应该在一个单独的模块中。 WordPress 已经有一个很好抽象的
WP_Query
类,应该用作示例。 - 所有的 HTML 都进入一个模板。 我们将在下面进一步介绍 PHP 模板。
- 剩余的 PHP 代码应该包装在一个函数中——如果代码对于一个函数来说太长或太复杂,则可以使用几个函数。
$id
等参数通过函数参数传递。
这是对上面示例的大大简化的重写:
function betterSiteSettings($args) { $data = WP_Settings_Query($args); // process $data here $context = array_merge([], $data_processed, $other_data); return Template::render('template.name', $context); }
现代 WordPress 开发最佳实践 #2:避免使用全局变量
WordPress 有太多的全局变量。 为什么全局变量不好? 它们使您的 WordPress PHP 代码难以遵循并使应用程序状态不可靠。 任何一段 PHP 代码——也就是安装在 WordPress 中的任何插件——都可以读取和写入全局变量,因此不能保证它们包含有效数据。 试图了解在 Loop 之类的东西中使用了哪些全局变量也不是一件容易的事。
让我们从实际的角度来看这个。 这个例子来自 WooCommerce。 可能每个 WordPress 开发人员都知道它是什么——循环:
<?php while ( have_posts() ) : the_post(); ?> <?php wc_get_template_part( 'content', 'single-product' ); ?> <?php endwhile; // end of the loop. ?>
上面的代码片段呈现了一个产品模板。 鉴于没有传递给wc_get_template_part
的参数,它如何知道要显示什么产品? 查看模板,我们看到它以global $product;
开头。 ,所以这是当前产品对象的存储位置。
现在想象一下,我们有一个搜索和过滤产品的目录页面,我们希望在同一页面上显示“产品详细信息”弹出窗口。 在幕后,前端脚本执行 AJAX 请求以获取该特定产品模板。 我们不能简单地调用wc_get_template_part('content', 'single-product')
因为它不使用参数,所以我们需要设置几个全局变量才能使其工作。
更复杂的用例将涉及多个模板、在这些模板中触发的挂钩以及将其回调添加到这些挂钩的第三方插件。 它可以迅速升级。 我们无法知道这些回调所依赖的全局状态。 第三方插件可以自由修改其回调中的任何全局变量。 我们没有使用系统,而是开始与系统抗争,遇到来自不可靠的全局状态的奇怪错误。
将产品 ID 作为参数传递不是更明智吗? 然后我们可以重用该模板,而不必担心会弄乱 WordPress 使用的全局变量。
现代 WordPress 开发最佳实践 #3:使用面向对象编程 (OOP)
模块化导致了对象和面向对象编程的概念。 在最基本的层面上,OOP 是一种组织代码的方式。 函数和变量被捆绑到类中,分别称为类方法和属性。 WordPress 插件手册建议使用 OOP 来组织您的 WordPress 自定义 PHP 代码。
OOP 中的一个重要原则是限制对方法和属性的访问——或者在 PHP 术语中,将它们表示为private
或protected
的——因此只有其他类方法可以访问和更改它们。 对此的 OOP 术语是封装:数据被封装在类中,更改该数据的唯一方法是使用提供的类方法。
这使得调试和维护代码比使用可以在整个代码库中的任何位置修改的全局变量时容易得多。 考虑全局 WordPress post
变量。 您可以在代码中的任何位置访问它,并且许多功能都依赖于使用它。 如果您可以将修改仅限于 WordPress 核心功能,但任何人都可以阅读,该怎么办? 将全局post
变量隐藏或封装在一个类中并围绕它构建一个接口将使这成为可能。
这只是对 OOP 以及如何在现代 WordPress 开发中使用它的一个非常基本的描述。 为了进一步的学习,我彻底推荐 Carl Alexander 的电子书,使用 WordPress 发现面向对象编程,其中包含关于 WordPress 中 OOP 主题的最全面和有用的内容。
重要的是要记住,OOP 不是灵丹妙药:使用 OOP 编写糟糕的代码就像使用任何其他编程范式一样容易。
让我们深入了解一些关于使用 PHP 进行 WordPress 开发的具体建议。
现代 PHP 最佳实践 #1:目标 PHP 7.0+
使用现代 PHP 功能需要一个现代版本的 PHP。 根本没有理由支持低于 7.0 的 PHP 版本。 即使是 WordPress 核心,最早也要到 2019 年底才需要 PHP 7.0。
不过,最好检查您的最低版本以避免在不兼容的环境中出现“死机白屏”。 下面的代码片段显示了使用插件标头在代码中声明具有保护条件的最低 PHP 版本。
<?php /** * Plugin Name: My Awesome Plugin * Requires PHP: 7.0 */ // bails if PHP version is lower than required if (version_compare(PHP_VERSION, '7.0.0', '<')) { // add admin notice here return; } // the rest of the actual plugin here
现代 PHP 最佳实践 #2:采用 PHP 行业标准(PSR-2 编码风格指南)
PSR 是 PHP Framework Interop Group 发布的建议。 它们是任何现代 PHP 工作流程中事实上的行业标准,可以肯定地说,整个 PHP 社区都遵循这些标准。 PSR-2 是描述编码风格的建议。 Symfony 和 Laravel 等流行的 PHP 框架遵循 PSR-2。
为什么要使用 PSR-2 而不是 WordPress 编码标准? 主要是因为 WordPress 标准已经过时并且不使用任何较新的语言功能。 这是可以理解的,因为 WordPress 核心必须遵循自己的标准。 直到最近它才支持 PHP 5.2,而且 PSR-2 与 PHP 5.2 不兼容。

这可能并不明显,但除非您致力于核心,否则不需要使用 WordPress 编码标准。 将遵循 PSR-2 标准的插件提交到 WordPress 插件目录是没有问题的。 事实上,这样做有一些很好的论据。
现代 PHP 最佳实践 #3:使用 PHP 模板引擎
PHP 不是模板引擎。 它最初是一个,但后来演变成一种功能齐全的编程语言,没有理由继续将它用于模板。 PHP 最流行的两个模板引擎是 Twig 和 Blade,它们分别被 Symfony 和 Laravel 使用。 本文将使用 Twig 作为模板引擎的示例; 但是,Blade 具有类似的特性和功能。 我恳请您考虑两者并自己决定最适合您的。
下面的示例比较了一个 PHP 模板及其对应的 Twig 模板。 在 PHP 示例中显示和转义输出特别冗长:
foreach ( $options as $option ) { ?> <tr class="form-field"> <th scope="row"> <label for="<?php echo esc_attr( $option->option_name ); ?>"> <?php echo esc_html( strtolower( $option->option_name ) ); ?> </label> </th> </tr> <?php } // End foreach
在 Twig 中,这更简洁易读:
{% for option in options %} <tr class="form-field"> <th scope="row"> <label for="{{ option.option_name }}"> {{ option.option_name }} </label> </th> </tr> {% endfor %}
Twig 的主要优点是:
- 可读且简洁的语法
- 自动输出转义
- 通过继承和块扩展模板
在性能方面,Twig 编译为 PHP 模板并且几乎没有开销。 Twig 只有一个 PHP 语言结构的子集,仅限于模板。 这迫使开发人员从模板中删除业务逻辑,从而强制分离关注点。
甚至还有用于 WordPress 的 Twig。 它被称为 Timber,它是开始创建更好模板的好方法。 Timber starter 主题是以 OOP 方式组织主题的完美示例。
现代 PHP 最佳实践 #4:使用 Composer
Composer 是 PHP 的依赖管理器。 它是一个工具,允许声明项目正在使用的库,然后自动下载、安装和更新。 然后你只需要包含 Composer 的自动加载文件vendor/autoload.php
而不是手动要求每个库。
WordPress 插件和主题通常不使用任何第三方库。 这部分是因为 WordPress 有一个广泛的 API 几乎可以满足任何需求,部分是因为可能的版本冲突。 考虑两个需要相同 PHP 库但版本不同的插件。 第一个运行的插件获得正确的版本,第二个插件也获得该版本。 这很可能是另一种死机白屏情况。
为避免冲突,应在应用程序级别(即整个 WordPress 站点)使用依赖关系管理。 这就是 Roots(更具体地说是基岩)所做的。 在包(插件或主题)级别使用时,Composer 在使用第三方库时可能会导致冲突。 这是一个已知问题。 迄今为止存在的唯一解决方案是将外部库的名称空间重命名为独特的名称,这不是一项简单的任务。
不过 Composer 仍有一个用途:自动加载您自己的类。 但在我们进一步使用自动加载之前,我们需要了解 PHP 命名空间。
现代 PHP 最佳实践 #5:使用命名空间
WordPress 核心是一个遗留项目,它使用全局命名空间,或者换句话说,根本没有命名空间。 全局声明的任何类或函数(意味着不在另一个类或函数中)在整个代码库的任何地方都是可见的。 它们的名称不仅在您的代码库中而且对于现在使用或将来可能使用的所有插件和主题都必须是唯一的。
命名冲突(例如,用已经存在的名称声明一个函数)通常会导致死机白屏,我们不希望这样。 WordPress Codex 建议为所有函数和类添加一些独特的前缀。 因此,我们没有像Order
这样的简单类名,而是得到像Akrte_Awesome_Plugin_Order
这样的东西,其中“Akrte”是我刚刚编写的唯一前缀。
如果我们使用文件系统类比,命名空间可以被视为组或文件夹,它有助于组织代码并避免名称冲突。 您可以拥有用斜杠分隔的复杂命名空间,就像嵌套文件夹一样。 (PHP 命名空间特别使用反斜杠。)
这些命名空间部分称为子命名空间。 如果使用命名空间完成,我们的示例类Akrte_Awesome_Plugin_Order
将是Akrte\Awesome_Plugin\Order
。 这里Akrte
和Awesome_Plugin
是命名空间部分(或子命名空间),而Order
是类名。 然后你可以添加一个use
语句,然后只使用类名。 它肯定看起来更好:
use Akrte\Awesome_Plugin\Order; $a = new Order;
显然,命名空间应该是唯一的; 因此,我们应该给第一个“根”子命名空间一个唯一的名称,通常是供应商名称。 例如,WooCommerce 类WC_REST_Order_Notes_V2_Controller
可以使用如下命名空间重新完成:
namespace WooCommerce\RestApi\V2\Controllers; class OrderNotes {}
WooCommerce 代码库现在确实使用命名空间。 例如,在 WooCommerce REST API 版本 4 中。
现代 PHP 最佳实践 #6:使用自动加载器
在大多数 PHP 工作流程中,将 PHP 文件链接在一起的常用方法是使用require
或include
语句。 随着项目的增长,您会在主插件文件中获得数十个require
语句。 自动加载器会自动包含文件,并且仅在需要时才会这样做。 从技术上讲,它是一个函数,当它第一次在代码中遇到时,它require
包含一个类或函数的 sa 文件。 不再需要手动添加任何require
语句。
由于自动加载器仅加载特定请求中使用的模块,因此通常还会显着提高性能。 如果没有自动加载器,即使请求仅使用 10% 的代码,您的整个代码库也会包含在内。
自动加载器函数需要知道您的类和函数存在于哪些文件中。 为此,有一个 PHP-FIG 标准 PSR-4。
它表示命名空间的一部分,即前缀,被指定为对应于基本文件夹。 其后的子命名空间对应于基本文件夹中的文件夹。 最后,类名对应文件名。 示例类Akrte\AwesomePlugin\Models\Order
需要以下文件夹结构:
/awesome-plugin awesome-plugin.php /includes /Models Order.php
命名空间前缀Akrte\AwesomePlugin\
对应于下面讨论的自动加载器配置中指定的includes
文件夹。 Models
子命名空间有一个对应的Models
文件夹, Order
类包含在Order.php
中。
幸运的是,不需要自己实现自动加载器功能。 Composer 可以为您创建一个自动加载器:
- 安装作曲家
- 在项目的根文件夹中创建一个
composer.json
文件。 它应该包含以下几行:
{ "name": "vendor-name/plugin-name", "require": {}, "autoload": { "psr-4": { "Akrte\\AwesomePlugin\\": "includes/" } } }
- 运行
composer install
。 - 在主插件 PHP 文件的顶部包含
vendor/autoload.php
,如下所示:
<?php /** * Plugin Name: My Awesome Plugin */ defined('ABSPATH') || exit; require __DIR__ . '/vendor/autoload.php'; // do stuff
除了命名空间,WooCommerce 的最新代码库还使用了 Composer 自动加载器。
在涵盖了这些 PHP 设计原则之后,是时候将我们所有的 PHP 课程与 WordPress 后端定制联系起来,并提出一个最终建议。
现代 WordPress 开发最佳实践 #4:考虑使用根堆栈
Roots 是目前最全面的现代 WordPress 开发工作流程。 但是,我会说它不一定要在每个WordPress 项目中使用,因为:
- Roots 必须从一开始就使用。 最常见的原因,真的。 重构现有项目的成本太高。
- 是有主见的。 同意时好,不同意时坏。 例如,您可能更喜欢其他组织主题的方式。 自以为是的项目也需要时间来学习“他们的方式”。
- 不是每个人都知道。 追随您以维护您使用 Roots 堆栈构建的站点的开发人员可能不知道它是什么,并且想知道 WordPress 文件夹发生了什么。 我们应该考虑一下我们的 WordPress 开发人员。
一般来说,在承诺使用它之前,您会希望充分了解任何强烈自以为是的项目的所有优点和缺点。
现代 PHP 和软件原理:使 WordPress 后端开发更强大
很明显,没有一种真正的编写软件的方法。 诸如关注点分离之类的概念已有数十年的历史。 然而,它的实际含义一直存在争议。 以 CSS 为例。 一开始,我们将它作为style
内联到 HTML 中,然后我们决定单独的 CSS 表是关注点分离的意义所在。
快进十年:当今的 JavaScript 应用程序使用组件作为关注点分离的实现。 前端开发人员倾向于使用 CSS-in-JS,这基本上意味着再次将 CSS 内联到 HTML 中(嗯,这不是那么简单,但你明白了)。 圈子就完成了!
最佳实践一直是关于改善开发者体验:
必须编写程序供人们阅读,并且只是偶然地供机器执行。
Abelson & Sussman,计算机程序的结构和解释
本 PHP WordPress 教程中的一些实践在您的项目中快速且易于实施。 例如,自动加载器:每个项目执行一次,然后尽情享受。 另一方面,新的软件架构理念需要时间、实践和无数次迭代才能变得方便和舒适。 不过,回报要大得多。 你不仅会更有效率地做你所做的事情,而且会更享受你所做的事情。 定期享受您为客户所做的工作也许是可持续发展的唯一途径。