跨版本PHP代码转换终极教程

跨版本PHP代码转换终极教程

在理想情况下,我们应该为我们的所有网站使用PHP8.0(撰写本文时的最新版本),并在新版本发布后立即进行更新。但是,开发人员通常需要使用以前的PHP版本,例如为WordPress创建公共插件或使用妨碍升级Web服务器环境的遗留代码时。

在这种情况下,我们可能会放弃使用最新PHP代码的希望。但是还有一个更好的选择:我们仍然可以使用PHP8.0编写源代码,并将其转换到以前的PHP版本,甚至是PHP7.1。

在本指南中,我们将教您有关转换PHP代码的所有知识。

  1. 什么是Transpiling?
  2. Transpiling PHP的优势
  3. PHP转换器(PHP Transpilers)
  4. 转换到哪一个PHP版本
  5. Transpiling vs Backporting
  6. Transpiled PHP示例
  7. 转换PHP的利弊
  8. 如何转换PHP
  9. 优化转换过程
  10. 转换代码时要避免的坑
  11. 转换和连续集成
  12. 测试转换代码

什么是Transpiling?

Transpiling将源代码从编程语言转换为相同或不同编程语言的等效源代码。

Transpiling并不是Web开发中的一个新概念:客户端开发人员很可能熟悉Babel,JavaScript代码的转换器。

Babel将现代ECMAScript 2015+版本中的JavaScript代码转换为与旧浏览器兼容的旧版本。例如,给定ES2015箭头函数:

[2, 4, 6].map((n) => n * 2);
[2, 4, 6].map((n) => n * 2);
[2, 4, 6].map((n) => n * 2);

…Babel将其转换为ES5版本:

[2, 4, 6].map(function(n) {
return n * 2;
});
[2, 4, 6].map(function(n) {
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]);
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);

…可以转换为其等效的PHP 7.3版本:

$nums = array_map(
function ($n) {
return $n * 2;
},
[2, 4, 6]
);
$nums = array_map(
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(float|int $param): string|float|int|null
{
// …
}
function someFunction(float|int $param): string|float|int|null
{
  // ...
}

在这些情况下,只要开发需要新特性,而不是生产需要新特性,就仍然可以进行转换。然后,我们可以简单地从转换的代码中完全删除该特性,而不会产生严重后果。

联合类型就是这样一个例子。此功能用于检查输入类型与其提供的值之间是否不匹配,这有助于防止错误。如果与类型发生冲突,那么开发中就会出现错误,我们应该在代码到达生产环境之前捕获并修复它。

因此,我们可以从生产代码中删除该功能:

function someFunction($param)
{
// …
}
function someFunction($param)
{
// …
}
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%:

跨版本PHP代码转换终极教程

WordPress使用情况统计(按版本)图像来源:WordPress

因此,面向全球受众的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中介绍:

#[SomeAttr]
function someFunc() {}
#[AnotherAttr]
class SomeClass {}
#[SomeAttr]
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 版本 特征
7.1 所有都可以
7.2 – object type
– parameter type widening
– PREG_UNMATCHED_AS_NULL flag in preg_match
7.3 – Reference assignments in list() / array destructuring (Except inside foreach — #4376)
– Flexible Heredoc and Nowdoc syntax
– Trailing commas in functions calls
– set(raw)cookie accepts $option argument
7.4 – Typed properties
– 
Arrow functions
– 
Null coalescing assignment operator
– 
Unpacking inside arrays
– 
Numeric literal separator
– 
strip_tags() with array of tag names
– 
covariant return types and contravariant param types
8.0 – Union types
– 
mixed pseudo type
– 
static return type
– 
::class magic constant on objects
– 
match expressions
– 
catch exceptions only by type
– 
Null-safe operator
– 
Class constructor property promotion
– 
Trailing commas in parameter lists and closure use lists

PHP转换器(PHP Transpilers)

目前,有一个用于转换PHP代码的工具:Rector

Rector是一个PHP重构工具,它根据可编程规则转换PHP代码。我们输入源代码和要运行的规则集,Rector将转换代码。

Rector通过命令行操作,通过Composer安装在项目中。执行时,Rector将在转换前后输出代码的“diff”(添加为绿色,删除为红色):

跨版本PHP代码转换终极教程

来自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 (!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;
}
}
}
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;
}
}
}
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 接口:
  • Stringable

类:

  • ValueError
  • UnhandledMatchError

函数:

  • FILTER_VALIDATE_BOOL

功能:

Transpiled PHP示例

让我们一起来看看几个转换PHP代码的示例,以及几个正在完全转换的包。

PHP代码

match 表达式是在PHP8.0中引入的。此源代码:

function getFieldValue(string $fieldName): ?string
{
return match($fieldName) {
‘foo’ => ‘foofoo’,
‘bar’ => ‘barbar’,
‘baz’ => ‘bazbaz’,
default => null,
};
}
function getFieldValue(string $fieldName): ?string
{
return match($fieldName) {
‘foo’ => ‘foofoo’,
‘bar’ => ‘barbar’,
‘baz’ => ‘bazbaz’,
default => null,
};
}

                
重要声明

本网站的文章部分内容可能来源于网络,如有侵犯你的权益请联系邮箱:wxzn8@outlook.com
站内资源为网友个人学习或测试研究使用,未经原版权作者许可,禁止用于任何商业途径!请在下载24小时内删除!本站资源大多存储在云盘,如发现链接失效请反馈,我们会及时更新。

给TA打赏
共{{data.count}}人
人已打赏
WordPress开发学习

在JavaScript中使用媒体查询的教程

2023-1-13 18:58:12

WordPress开发学习

40个最常用的Linux命令行大全

2023-1-13 18:58:34

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索