使用现代 PHP 改进 WordPress 代码

已发表: 2022-03-10
快速总结 ↬由于向后兼容性,WordPress 没有利用 PHP 5.2.4 之后发布的新 PHP 功能。 幸运的是,WordPress 很快将需要 PHP 5.6+,甚至在不久之后的 PHP 7.0+。 本文将介绍 WordPress 新可用的 PHP 功能,并尝试建议如何使用这些功能来生产更好的软件。

WordPress 诞生于 15 年前,由于它历来保留了向后兼容性,其代码的较新版本无法充分利用较新版本的 PHP 提供的最新功能。 虽然 PHP 的最新版本是 7.3.2,但 WordPress 仍然提供最高到 PHP 5.2.4 的支持。

但那些日子很快就会过去! WordPress 将升级其最低 PHP 版本支持,在 2019 年 4 月升级到 PHP 5.6,在 2019 年 12 月升级到 PHP 7(如果一切按计划进行的话)。 然后,我们终于可以开始使用 PHP 的命令式编程功能,而不必担心破坏我们客户的网站。 欢呼!

由于 WordPress 15 年的功能代码影响了开发人员使用 WordPress 构建的方式,因此我们的网站、主题和插件可能会充斥着不太理想的代码,这些代码可以很高兴地接受升级。

本文由两部分组成:

  1. 最相关的新功能
    PHP 版本 5.3、5.4、5.5、5.6 和 7.0 中添加了更多功能(请注意,没有 PHP 6),我们将探索最相关的功能。
  2. 构建更好的软件
    我们将仔细研究这些功能以及它们如何帮助我们构建更好的软件。

让我们从探索 PHP 的“新”特性开始。

跳跃后更多! 继续往下看↓

类、OOP、SOLID 和设计模式

PHP 5 中添加了类和对象,因此 WordPress 已经使用了这些功能,但是,不是非常广泛或全面: WordPress 中的编码范例主要是函数式编程(通过调用没有应用程序状态的函数来执行计算)而不是对象面向编程(OOP)(通过操纵对象的状态来执行计算)。 因此,我还描述了类和对象以及如何通过 OOP 使用它们。

OOP 是生成模块化应用程序的理想选择:类允许创建组件,每个组件都可以实现特定的功能并与其他组件交互,并且可以通过其封装的属性和继承提供自定义,从而实现高度的代码可重用性。 因此,应用程序的测试和维护成本更低,因为可以将各个功能与应用程序隔离并自行处理; 由于开发人员可以使用已经开发的组件并避免为每个应用程序重新发明轮子,因此还可以提高生产力。

一个类具有属性和功能,可以通过使用private (只能从定义类中访问)、 protected (可以从定义类及其祖先类和继承类中访问)和public (从任何地方访问)来获得可见性。 在函数中,我们可以通过在其名称前加上$this->来访问类的属性:

 class Person { protected $name; public function __construct($name) { $this->name = $name; } public function getIntroduction() { return sprintf( __('My name is %s'), $this->name ); } }

一个类通过new关键字实例化为一个对象,之后我们可以通过->访问它的属性和函数:

 $person = new Person('Pedro'); echo $person->getIntroduction(); // This prints "My name is Pedro"

继承类可以覆盖其祖先类的publicprotected函数,并通过在它们前面加上parent::来访问祖先函数:

 class WorkerPerson extends Person { protected $occupation; public function __construct($name, $occupation) { parent::__construct($name); $this->occupation = $occupation; } public function getIntroduction() { return sprintf( __('%s and my occupation is %s'), parent::getIntroduction(), $this->occupation ); } } $worker = new WorkerPerson('Pedro', 'web development'); echo $worker->getIntroduction(); // This prints "My name is Pedro and my occupation is web development"

可以将方法设为abstract ,这意味着它必须由继承类实现。 包含abstract方法的类本身必须是abstract的,这意味着它不能被实例化; 只有实现抽象方法的类可以被实例化:

 abstract class Person { abstract public function getName(); public function getIntroduction() { return sprintf( __('My name is %s'), $this->getName() ); } } // Person cannot be instantiated class Manuel extends Person { public function getName() { return 'Manuel'; } } // Manuel can be instantiated $manuel = new Manuel();

类还可以定义static方法和属性,它们存在于类本身之下,而不是作为对象的类的实例化之下。 这些可以通过类内部的self::访问,也可以通过类的名称 + ::从外部访问:

 class Factory { protected static $instances = []; public static function registerInstance($handle, $instance) { self::$instances[$handle] = $instance; } public static function getInstance($handle) { return self::$instances[$handle]; } } $engine = Factory::getInstance('Engine');

为了充分利用 OOP,我们可以使用 SOLID 原则为应用程序建立完善且易于定制的基础,并使用设计模式以经过试验和测试的方式解决特定问题。 设计模式是标准化的和有据可查的,使开发人员能够了解应用程序中的不同组件如何相互关联,并提供一种以有序方式构建应用程序的方法,这有助于避免使用全局变量(例如global $wpdb )污染全球环境。

命名空间

命名空间被添加到 PHP 5.3,因此它们目前完全从 WordPress 核心中消失。

命名空间允许在结构上组织代码库以避免在不同项目具有相同名称时发生冲突——其方式类似于操作系统目录,只要它们存储在不同的目录中,就允许具有相同名称的不同文件。 命名空间对 PHP 项目(例如类、特征和接口)执行相同的封装技巧,通过将它们放在不同的命名空间中来避免在不同项目具有相同名称时发生冲突。

与第三方库交互时,命名空间是必须的,因为我们无法控制它们的项目将如何命名,从而导致在我们的项目使用标准名称(例如“文件”、“记录器”或“上传器”)时可能发生冲突。 此外,即使在单个项目中,命名空间也可以防止类名变得过长,以避免与其他类发生冲突,这可能导致名称如“MyProject_Controller_FileUpload”。

命名空间是使用关键字namespace空间定义的(在打开<?php之后立即放置在行上),并且可以跨越多个级别或子命名空间(类似于放置文件的多个子目录),它们使用\分隔:

 <?php namespace CoolSoft\ImageResizer\Controllers; class ImageUpload { }

要访问上述类,我们需要完全限定其名称,包括其命名空间(并以\开头):

 $imageUpload = new \CoolSoft\ImageResizer\Controllers\ImageUpload();

或者我们也可以将类导入到当前上下文中,之后我们可以直接通过其名称引用该类:

 use CoolSoft\ImageResizer\Controllers\ImageUpload; $imageUpload = new ImageUpload();

通过按照既定约定命名命名空间,我们可以获得额外的好处。 例如,通过遵循 PHP 标准建议 PSR-4,应用程序可以使用 Composer 的自动加载机制来加载文件,从而降低复杂性并增加依赖项之间的无摩擦互操作性。 该约定规定将供应商名称(例如公司名称)作为顶部子名称空间,可选地后跟包名称,然后才跟一个内部结构,其中每个子名称空间对应于具有相同名称的目录。 结果将驱动器中文件的物理位置与文件中定义的元素的命名空间一一对应。

性状

特性已添加到 PHP 5.4,因此它们目前完全从 WordPress 核心中消失。

PHP 支持单一继承,因此子类从一个父类派生,而不是从多个父类派生。 因此,不相互扩展的类不能通过类继承重用代码。 Traits 是一种能够实现行为水平组合的机制,使得在不同类层次结构中的类之间重用代码成为可能。

特征类似于类,但是它不能单独实例化。 相反,可以认为在 trait 中定义的代码在编译时被“复制并粘贴”到组合类中。

使用trait关键字定义特征,之后可以通过use关键字将其导入任何类。 在下面的示例中,两个完全不相关的类PersonShop可以通过 trait Addressable重用相同的代码:

 trait Addressable { protected $address; public function getAddress() { return $this->address; } public function setAddress($address) { $this->address = $address; } } class Person { use Addressable; } class Shop { use Addressable; } $person = new Person('Juan Carlos'); $person->setAddress('Obelisco, Buenos Aires');

一个类也可以构成多个特征:

 trait Exportable { public class exportToCSV($filename) { // Iterate all properties and export them to a CSV file } } class Person { use Addressable, Exportable; }

特征也可以由其他特征组成,定义抽象方法,并在两个或多个组合特征具有相同的函数名称时提供冲突解决机制,以及其他特性。

接口

PHP 5 中添加了接口,因此 WordPress 已经使用了这个功能,但是非常谨慎:核心总共包含不到十个接口!

接口允许创建指定必须实现哪些方法的代码,但不必定义这些方法是如何实际实现的。 它们对于定义组件之间的契约很有用,这可以提高应用程序的模块化和可维护性:实现接口的类可以是一个黑盒代码,只要接口中函数的签名不改变,代码可以随意升级而不会产生重大更改,这有助于防止技术债务的积累。 此外,它们可以通过允许将某些接口的实现交换到不同供应商的实现来帮助减少供应商锁定。 因此,必须针对接口而不是实现对应用程序进行编码(并通过依赖注入定义哪些是实际实现)。

接口是使用interface关键字定义的,并且必须仅列出其方法的签名(即没有定义其内容),它必须具有public可见性(默认情况下,添加不可见性关键字也会使其公开):

 interface FileStorage { function save($filename, $contents); function readContents($filename); }

一个类定义它通过implements关键字实现接口:

 class LocalDriveFileStorage implements FileStorage { function save($filename, $contents) { // Implement logic } function readContents($filename) { // Implement logic } }

一个类可以实现多个接口,用,分隔它们:

 interface AWSService { function getRegion(); } class S3FileStorage implements FileStorage, AWSService { function save($filename, $contents) { // Implement logic } function readContents($filename) { // Implement logic } function getRegion() { return 'us-east-1'; } }

由于接口声明了组件应该做什么的意图,因此适当地命名接口非常重要。

闭包

闭包被添加到 PHP 5.3,因此它们目前完全从 WordPress 核心中消失。

闭包是一种实现匿名函数的机制,它有助于将全局命名空间与一次性(或很少使用)函数分开。 从技术上讲,闭包是类Closure的实例,然而,在实践中,我们很可能很高兴地没有意识到这一事实而没有任何伤害。

在闭包之前,每当将一个函数作为参数传递给另一个函数时,我们必须提前定义函数并将其名称作为参数传递:

 function duplicate($price) { return $price*2; } $touristPrices = array_map('duplicate', $localPrices);

使用闭包,匿名(即没有名称)函数已经可以直接作为参数传递:

 $touristPrices = array_map(function($price) { return $price*2; }, $localPrices);

闭包可以通过use关键字将变量导入其上下文:

 $factor = 2; $touristPrices = array_map(function($price) use($factor) { return $price*$factor; }, $localPrices);

发电机

生成器被添加到 PHP 5.5,因此它们目前完全从 WordPress 核心中消失。

生成器提供了一种简单的方法来实现简单的迭代器。 生成器允许编写使用foreach迭代一组数据的代码,而无需在内存中构建数组。 生成器函数与普通函数相同,不同之处在于它不是返回一次,而是可以根据需要多次yield以提供要迭代的值。

 function xrange($start, $limit, $step = 1) { for ($i = $start; $i <= $limit; $i += $step) { yield $i; } } foreach (xrange(1, 9, 2) as $number) { echo "$number "; } // This prints: 1 3 5 7 9

参数和返回类型声明

在不同版本的 PHP 中引入了不同的参数类型声明:WordPress 已经能够声明接口和数组(它没有:我几乎没有在核心中找到一个将数组声明为参数的函数实例,并且没有接口),并且将很快就能声明可调用对象(PHP 5.4 中添加)和标量类型:bool、float、int 和字符串(PHP 7.0 中添加)。 返回类型声明被添加到 PHP 7.0。

参数类型声明允许函数声明参数必须是什么特定类型。 验证在调用时执行,如果参数的类型不是声明的类型,则抛出异常。 返回类型声明是相同的概念,但是,它们指定将从函数返回的值的类型。 类型声明有助于使函数的意图更易于理解,并避免接收或返回意外类型导致的运行时错误。

参数类型在参数变量名之前声明,返回类型在参数之后声明,前面是:

 function foo(boolean $bar): int { }

标量参数类型声明有两个选项:强制和严格。 在强制模式下,如果作为参数传递了错误的类型,它将被转换为正确的类型。 例如,为期望字符串的参数提供整数的函数将获得字符串类型的变量。 在严格模式下,仅接受具有确切声明类型的变量。

强制模式是默认设置。 要启用严格模式,我们必须添加与strict_types声明一起使用的declare语句:

 declare(strict_types=1); function foo(boolean $bar) { }

新语法和运算符

WordPress 已经可以通过函数func_num_args识别变长参数列表。 从 PHP 5.6 开始,我们可以使用...标记来表示函数接受可变数量的参数,这些参数将作为数组传递给给定的变量:

 function sum(...$numbers) { $sum = 0; foreach ($numbers as $number) { $sum += $number; } return $sum; }

从 PHP 5.6 开始,常量可以涉及涉及数字和字符串文字的标量表达式,而不仅仅是静态值,还包括数组:

 const SUM = 37 + 2; // A scalar expression const LETTERS = ['a', 'b', 'c']; // An array

从 PHP 7.0 开始,也可以使用define数组:

 define('LETTERS', ['a', 'b', 'c']);

PHP 7.0 添加了几个新的运算符:Null 合并运算符 ( ?? ) 和 Spaceship 运算符 ( <=> )。

Null 合并运算符?? 是需要将三元与isset() 结合使用的常见情况的语法糖。 如果存在且不为 NULL,则返回其第一个操作数; 否则,它返回它的第二个操作数。

 $username = $_GET['user'] ?? 'nobody'; // This is equivalent to: // $username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

Spaceship 运算符<=>用于比较两个表达式,当第一个操作数分别小于、等于或大于第二个操作数时返回 -1、0 或 1。

 echo 1 <=> 2; // returns -1 echo 1 <=> 1; // returns 0 echo 2 <=> 1; // returns 1

这些是 PHP 5.3 到 7.0 版本中添加的最重要的新特性。 可以通过浏览 PHP 的关于从一个版本迁移到另一个版本的文档来获得本文未列出的其他新特性的列表。

接下来,我们将分析如何充分利用所有这些新功能,以及 Web 开发的最新趋势,以生产出更好的软件。

PHP 标准建议

PHP 标准建议是由一群来自流行框架和库的 PHP 开发人员创建的,他们试图建立约定,以便不同的项目可以更无缝地集成,不同的团队可以更好地相互合作。 这些建议不是静态的:现有的建议可能会被弃用,而新的建议可能会取代它们,新的建议会持续发布。

目前的建议如下:

团体推荐描述
编码风格
标准化格式可减少阅读其他作者的代码时的认知摩擦
PSR-1 基本编码标准
PSR-2 编码风格指南
自动加载
自动加载器通过将命名空间映射到文件系统路径来消除包含文件的复杂性
PSR-4 改进的自动加载
接口
接口通过遵循预期的契约简化了项目之间的代码共享
PSR-3 记录器接口
PSR-6 缓存接口
PSR-11 容器接口
PSR-13 超媒体链接
PSR-16 简单缓存
HTTP
可互操作的标准和接口,在客户端和服务器端有一种不可知的方法来处理 HTTP 请求和响应
PSR-7 HTTP 消息接口
PSR-15 HTTP 处理程序
PSR-17 HTTP 工厂
PSR-18 HTTP 客户端

在组件中思考和编码

组件可以使用框架中的最佳功能,而不必局限于框架本身。 例如,Symfony 已作为一组可重用的 PHP 组件发布,可以独立于 Symfony 框架安装; Laravel 是另一个 PHP 框架,它利用了几个 Symfony 组件,并发布了自己的一组可重用组件,可供任何 PHP 项目使用。

所有这些组件都发布在 Packagist(一个公共 PHP 包的存储库)中,并且可以通过 Composer(一个非常流行的 PHP 依赖项管理器)轻松添加到任何 PHP 项目中。

WordPress 应该是这样一个良性发展周期的一部分。 不幸的是,WordPress 核心本身不是使用组件构建的(几乎完全没有接口就证明了这一点),而且,它甚至没有通过 Composer 安装 WordPress 所需的composer.json文件。 这是因为 WordPress 社区尚未就 WordPress 是网站的依赖项(在这种情况下通过 Composer 安装它是合理的)还是网站本身(在这种情况下,Composer 可能不是该工作的正确工具)达成一致意见.

在我看来,如果我们希望 WordPress 在未来 15 年内保持相关性(至少 WordPress 作为后端 CMS),那么WordPress 必须被识别为站点的依赖项,并可以通过 Composer 进行安装。 原因很简单:在终端中只需一个命令,Composer 就可以从 Packagist 中发布的数千个包中声明和安装项目的依赖项,从而可以立即创建极其强大的 PHP 应用程序,开发人员喜欢以这种方式工作。 如果 WordPress 不适应这种模式,它可能会失去开发社区的支持并被遗忘,就像 FTP 在引入基于 Git 的部署后失宠一样。

我认为 Gutenberg 的发布已经证明 WordPress 是一个站点依赖项,而不是站点本身:Gutenberg 将 WordPress 视为无头 CMS,并且也可以与其他后端系统一起运行,正如 Drupal Gutenberg 所举例说明的那样。 因此,Gutenberg 明确表示支持站点的 CMS 是可交换的,因此应将其视为依赖项。 此外,Gutenberg 本身打算基于通过 npm 发布的 JavaScript 组件(正如核心提交者 Adam Silverstein 所解释的那样),因此如果希望 WordPress 客户端通过 npm 包管理器管理其 JavaScript 包,那么为什么不将此逻辑扩展到后端为了通过 Composer 管理 PHP 依赖项?

现在好消息:无需等待此问题得到解决,因为已经可以将 WordPress 视为站点的依赖项并通过 Composer 安装它。 John P. Bloch 在 Git 中镜像了 WordPress 核心,添加了 composer.json 文件,并在 Packagist 中发布了它,Roots 的 Bedrock 提供了一个安装 WordPress 的包,该包具有自定义的文件夹结构,支持现代开发工具和增强的安全性。 主题和插件也包括在内; 只要它们已列在 WordPress 主题和插件目录中,它们就可以在 WordPress Packagist 下使用。

因此,创建 WordPress 代码而不是考虑主题和插件,而是考虑组件是一个明智的选择,通过 Packagist 使它们可供任何 PHP 项目使用,另外打包和发布为主题和用于 WordPress 特定用途的插件。 如果组件需要与 WordPress API 交互,那么这些 API 可以抽象为一个接口,如果需要,也可以为其他 CMS 实现。

添加模板引擎以改进视图层

如果我们遵循在组件中思考和编码的建议,将 WordPress 视为站点本身而不是站点的依赖项,那么我们的项目就可以摆脱 WordPress 强加的界限,并从其他框架中引入思想和工具。

在服务器端呈现 HTML 内容就是一个很好的例子,它是通过普通的 PHP 模板完成的。 这个视图层可以通过模板引擎 Twig(Symfony)和 Blade(Laravel)来增强,它们提供了非常简洁的语法和强大的功能,使其优于普通的 PHP 模板。 特别是,Gutenberg 的动态块可以很容易地从这些模板引擎中受益,因为它们在服务器端呈现块的 HTML 的过程与 WordPress 的模板层次结构分离。

建筑师一般用途的应用程序

对接口进行编码并从组件的角度进行思考,使我们能够为一般用途构建应用程序,并针对我们需要交付的特定用途对其进行定制,而不是仅针对我们拥有的每个项目的特定用途进行编码。 尽管这种方法在短期内成本更高(它涉及额外的工作),但从长远来看,如果只需定制一个通用应用程序就可以以较低的工作量交付额外的项目,它就会得到回报。

为了使这种方法有效,必须考虑以下因素:

避免固定依赖(尽可能)

jQuery 和 Bootstrap(或 Foundation,或<–在此处插入您最喜欢的库–> )在几年前可能被认为是必备品,但是,它们在 vanilla JS 和更新的原生 CSS 功能面前逐渐失去优势。 因此,五年前编写的依赖于这些库的通用项目现在可能不再适用。 因此,作为一般经验法则,对第三方库的固定依赖数量越少,从长远来看,它就越是最新的。

功能的逐步增强

WordPress 是一个成熟的 CMS 系统,其中包括用户管理,因此对用户管理的支持是开箱即用的。 但是,并非每个 WordPress 站点都需要用户管理。 因此,我们的应用程序应该考虑到这一点,并在每种情况下优化工作:在需要时支持用户管理,但在不需要时不加载相应的资产。 这种方法也可以逐步发挥作用:假设客户需要实现联系我们表单但没有预算,因此我们使用功能有限的免费插件对其进行编码,而另一个客户有预算从商业插件产品购买许可证更好的功能。 然后,我们可以将我们的功能编码为默认为非常基本的功能,并越来越多地使用系统中功能最强大的插件中的功能。

持续的代码和文档审查

通过定期查看我们之前编写的代码及其文档,我们可以验证它是否是关于新约定和技术的最新版本,如果不是,则在技术债务变得过于昂贵而无法克服之前采取措施对其进行升级我们需要从头开始重新编码。

推荐阅读当心:可能使您的网站不安全的 PHP 和 WordPress 函数

尝试尽量减少问题,但在问题发生时做好准备

没有软件是 100% 完美的:错误总是存在的,只是我们还没有找到它们。 因此,我们需要确保一旦出现问题,它们很容易解决。

让它变得简单

复杂的软件无法长期维护:不仅因为其他团队成员可能不理解它,还因为编写它的人可能在几年后无法理解他/她自己的复杂代码。 所以生产简单的软件必须是首要任务,因为只有简单的软件才能正确和快速。

编译时失败比运行时失败好

如果一段代码可以在编译时或运行时验证错误,那么我们应该优先考虑编译时解决方案,这样错误就可以在开发阶段和应用程序投入生产之前出现并得到处理。 例如, constdefine都用于定义常量,然而, const在编译时验证, define在运行时验证。 因此,只要有可能,使用const优于define

遵循此建议,可以通过将实际类作为参数而不是带有类名的字符串传递来增强挂钩包含在类中的 WordPress 函数。 在下面的例子中,如果类Foo被重命名,而第二个钩子会产生编译错误,第一个钩子会在运行时失败,因此第二个钩子更好:

 class Foo { public static function bar() { } } add_action('init', ['Foo', 'bar']); // Not so good add_action('init', [Foo::class, 'bar']); // Much better

出于与上述相同的原因,我们应该避免使用全局变量(例如global $wpdb ):这些变量不仅会污染全局上下文并且不容易跟踪它们的来源,而且如果它们被重命名,错误将在运行时生成。 作为一种解决方案,我们可以使用依赖注入容器来获取所需对象的实例。

处理错误/异常

我们可以创建Exception对象的体系结构,以便应用程序可以根据每个特定问题做出适当的反应,尽可能从问题中恢复或在没有时向用户显示有用的错误消息,并且通常为管理员解决问题。 并始终保护您的用户免受白屏死机:所有未捕获的ErrorException都可以通过函数set_exception_handler拦截,以在屏幕上打印不可怕的错误消息。

采用构建工具

构建工具可以通过自动执行手动执行非常繁琐的任务来节省大量时间。 WordPress 不提供与任何特定构建工具的集成,因此将这些集成到项目中的任务将完全落在开发人员身上。

有不同的工具可以实现不同的目的。 例如,有构建工具来执行压缩和调整图像大小、缩小 JS 和 CSS 文件以及将文件复制到目录以生成发布的任务,例如 Webpack、Grunt 和 Gulp; 其他工具有助于创建项目的脚手架,这有助于为我们的主题或插件生成文件夹结构,例如 Yeoman。 事实上,有这么多工具,浏览比较不同可用工具的文章将有助于找到最适合我们需求的工具。

但是,在某些情况下,没有构建工具可以完全满足我们项目的需求,因此我们可能需要编写自己的构建工具作为项目本身的扩展。 例如,我这样做是为了生成 service-worker.js 文件,以在 WordPress 中添加对 Service Worker 的支持。

结论

由于非常强调保持向后兼容性,甚至扩展到 PHP 5.2.4,WordPress 无法从 PHP 中添加的最新功能中受益,这一事实使得 WordPress 成为一个不太令人兴奋的编码平台在许多开发人员中。

幸运的是,这些阴霾的日子可能很快就会过去,WordPress 可能会再次成为一个闪亮而令人兴奋的编码平台:从 2019 年 12 月开始的 PHP 7.0+ 要求将提供大量 PHP 功能,使开发人员能够生产更强大和更性能更高的软件。 在本文中,我们回顾了最重要的 PHP 新特性以及如何充分利用它们。

近期发布的 Gutenberg 可能是好时光即将到来的标志:即使 Gutenberg 本身还没有被社区完全接受,至少它表明了将最新技术(如 React 和 Webpack)纳入核心的意愿. 这一转折让我想:如果前端可以进行这样的改造,为什么不将其扩展到后端呢? 一旦 WordPress 至少需要 PHP 7.0,升级到现代工具和方法的速度就会加快:尽管 npm 成为首选的 JavaScript 包管理器,为什么不让 Composer 成为官方的 PHP 依赖项管理器呢? 如果块是在前端构建站点的新单元,为什么不使用 PHP 组件作为将功能合并到后端的单元呢? 最后,如果 Gutenberg 将 WordPress 视为可交换的后端 CMS,为什么不承认 WordPress 是站点依赖项而不是站点本身? 我会把这些悬而未决的问题留给你思考和思考。