在理想情况下,我们应该为我们的所有网站使用PHP8.0(撰写本文时的最新版本),并在新版本发布后立即进行更新。但是,开发人员通常需要使用以前的PHP版本,例如为WordPress创建公共插件或使用妨碍升级Web服务器环境的遗留代码时。
在这种情况下,我们可能会放弃使用最新PHP代码的希望。但是还有一个更好的选择:我们仍然可以使用PHP8.0编写源代码,并将其转换到以前的PHP版本,甚至是PHP7.1。
在本指南中,我们将教您有关转换PHP代码的所有知识。
- 什么是Transpiling?
- Transpiling PHP的优势
- PHP转换器(PHP Transpilers)
- 转换到哪一个PHP版本
- Transpiling vs Backporting
- Transpiled PHP示例
- 转换PHP的利弊
- 如何转换PHP
- 优化转换过程
- 转换代码时要避免的坑
- 转换和连续集成
- 测试转换代码
什么是Transpiling?
Transpiling将源代码从编程语言转换为相同或不同编程语言的等效源代码。
Transpiling并不是Web开发中的一个新概念:客户端开发人员很可能熟悉Babel,JavaScript代码的转换器。
Babel将现代ECMAScript 2015+版本中的JavaScript代码转换为与旧浏览器兼容的旧版本。例如,给定ES2015箭头函数:
[2, 4, 6].map((n) => n * 2);
…Babel将其转换为ES5版本:
return n * 2;
});
[2, 4, 6].map(function(n) { return n * 2; });
什么是Transpiling PHP?
Web开发中潜在的新功能是转换服务器端代码的可能性,特别是PHP。
转换PHP的工作方式与转换JavaScript的工作方式相同:现代PHP版本的源代码转换为旧PHP版本的等效代码。
下面是与前面相同的示例,PHP 7.4中的箭头函数:
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);
…可以转换为其等效的PHP 7.3版本:
function ($n) {
return $n * 2;
},
[2, 4, 6]
);
$nums = array_map( function ($n) { return $n * 2; }, [2, 4, 6] );
可以转换箭头函数,因为它们是语法糖,即生成现有行为的新语法。这是低垂的果实。
然而,也有一些新特性创建了一种新的行为,因此,对于以前版本的PHP不会有等效的代码。PHP 8.0中引入的联合类型就是这样:
{
// …
}
function someFunction(float|int $param): string|float|int|null { // ... }
在这些情况下,只要开发需要新特性,而不是生产需要新特性,就仍然可以进行转换。然后,我们可以简单地从转换的代码中完全删除该特性,而不会产生严重后果。
联合类型就是这样一个例子。此功能用于检查输入类型与其提供的值之间是否不匹配,这有助于防止错误。如果与类型发生冲突,那么开发中就会出现错误,我们应该在代码到达生产环境之前捕获并修复它。
因此,我们可以从生产代码中删除该功能:
{
// …
}
function someFunction($param) { // ... }
如果错误仍然发生在生产中,抛出的错误消息将不如使用联合类型时准确。然而,这一潜在的缺点被能够首先使用联合类型所抵消。
Transpiling PHP的优势
Transpiling使您能够使用最新版本的PHP编写应用程序,并生成一个在运行旧版本PHP的环境中也能工作的版本。
这对于为旧式内容管理系统(CMS)创建产品的开发人员特别有用。例如,WordPress仍然官方支持PHP5.6(尽管它推荐PHP7.4+)。运行PHP版本5.6到7.2的WordPress站点的百分比为34.8%,而运行PHP版本(8.0除外)的站点的百分比高达99.5%:
因此,面向全球受众的WordPress主题和插件很可能使用旧版本的PHP进行编码,以增加其可能的影响范围。多亏了transpiling,这些代码可以使用PHP8.0进行编码,并且仍然可以针对较旧的PHP版本发布,从而尽可能多地面向用户。
事实上,任何需要支持除最新版本以外的任何PHP版本(即使在当前支持的PHP版本范围内)的应用程序都可以从中受益。
Drupal就是这样,它需要PHP7.3。由于transpiling,开发人员可以使用PHP8.0创建公开可用的Drupal模块,并使用PHP7.3发布它们。
另一个例子是为由于某种原因而无法在其环境中运行PHP8.0的客户机创建自定义代码时。尽管如此,多亏了transpiling,开发人员仍然可以使用PHP8.0编写可交付成果,并在这些遗留环境中运行它们。
何时需要转换PHP(Transpile PHP)
PHP代码始终可以转换,除非它包含一些在之前的PHP版本中没有对等的PHP功能。
情况可能就是这样属性,在PHP 8.0中介绍:
function someFunc() {}
#[AnotherAttr]
class SomeClass {}
#[SomeAttr] function someFunc() {} #[AnotherAttr] class SomeClass {}
在前面使用箭头函数的示例中,可以转换代码,因为箭头函数是语法糖。相反,属性创建了全新的行为。PHP7.4及以下版本也可以复制这种行为,但只能通过手动编码,即不自动基于工具或流程(AI可以提供解决方案,但我们还没有)。
用于开发的属性,如#[Deprecated]
,可以用删除联合类型的相同方式删除。但是,不能删除在生产中修改应用程序行为的属性,也不能直接转换这些属性。
到目前为止,没有一个transpiler能够接受具有PHP8.0属性的代码并自动生成其等效PHP7.4代码。因此,如果您的PHP代码需要使用属性,那么转换它将是困难的或不可行的。
可转换PHP功能
这些是PHP7.1及以上版本的特性,目前可以转换。如果您的代码只使用这些特性,那么您可以确信您的转换应用程序将正常工作。否则,您将需要评估转换的代码是否会产生故障。
PHP转换器(PHP Transpilers)
目前,有一个用于转换PHP代码的工具:Rector。
Rector是一个PHP重构工具,它根据可编程规则转换PHP代码。我们输入源代码和要运行的规则集,Rector将转换代码。
Rector通过命令行操作,通过Composer安装在项目中。执行时,Rector将在转换前后输出代码的“diff”(添加为绿色,删除为红色):
转换到哪一个PHP版本
要跨PHP版本转换代码,必须创建相应的规则。
今天,Rector库包含PHP8.0到7.1范围内的大多数代码转换规则。因此,我们可以可靠地将PHP代码转换到7.1版。
也有从PHP7.1到7.0以及从7.0到5.6的转换规则,但这些规则并不详尽。完成这些代码的工作正在进行中,因此我们可能最终会将PHP代码转换到5.6版。
Transpiling vs Backporting
Backporting与Transpiling类似,但更简单。Backporting代码不一定依赖于语言的新特性。相反,只需从新版本的语言复制/粘贴/改编相应的代码,就可以为旧版本的语言提供相同的功能。
例如,PHP 8.0中引入了str_contains
函数。PHP 7.4及以下版本的相同功能可以像这样轻松实现:
if (!function_exists(‘str_contains’)) {
/**
* Checks if a string contains another
*
* @param string $haystack The string to search in
* @param string $needle The string to search
* @return boolean Returns TRUE if the needle was found in haystack, FALSE otherwise.
*/
function str_contains(string $haystack, string $needle): bool
{
return strpos($haystack, $needle) !== false;
}
}
}
if (!defined('PHP_VERSION_ID') || (defined('PHP_VERSION_ID') && PHP_VERSION_ID < 80000)) { if (!function_exists('str_contains')) { /** * Checks if a string contains another * * @param string $haystack The string to search in * @param string $needle The string to search * @return boolean Returns TRUE if the needle was found in haystack, FALSE otherwise. */ function str_contains(string $haystack, string $needle): bool { return strpos($haystack, $needle) !== false; } } }
因为Backporting比Transpiling更简单,所以无论何时进行Backporting,我们都应该选择这种解决方案。
关于PHP8.0到7.1之间的范围,我们可以使用Symfony的polyfill库:
这些库支持以下函数、类、常量和接口:
PHP 版本 | 特征 |
---|---|
7.2 | 功能:
函数: |
7.3 | 功能:
异常处理: |
7.4 | 功能: |
8.0 | 接口:
类:
函数:
功能: |
Transpiled PHP示例
让我们一起来看看几个转换PHP代码的示例,以及几个正在完全转换的包。
PHP代码
match
表达式是在PHP8.0中引入的。此源代码:
{
return match($fieldName) {
‘foo’ => ‘foofoo’,
‘bar’ => ‘barbar’,
‘baz’ => ‘bazbaz’,
default => null,
};
}