轉譯 PHP 代碼的終極指南

在理想情況下,我們應該為所有網站使用 PHP 8.0(撰寫本文時的最新版本),並在新版本發布後立即更新。但是,開發人員通常需要使用以前的 PHP 版本,例如在為 WordPress 創建公共插件或使用阻礙升級網路伺服器環境的遺留代碼時。

嘗試免費演示

在這些情況下,我們可以放棄使用最新的 PHP 代碼的希望。但是還有一個更好的選擇:我們仍然可以使用 PHP 8.0 編寫我們的源代碼並將其轉換為以前的 PHP 版本——甚至是 PHP 7.1。

在本指南中,我們將教您有關轉譯 PHP 代碼所需的一切知識。

什麼是轉譯?

轉譯將源代碼從一種編程語言轉換為相同或不同編程語言的等效源代碼。

轉譯在 Web 開發中並不是一個新概念:客戶端開發人員很可能熟悉 Babel,一種 JavaScript 代碼轉譯器。

Babel 將 JavaScript 代碼從現代 ECMAScript 2015+ 版本轉換為與舊瀏覽器兼容的舊版本。例如,給定一個 ES2015 箭頭函數:

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

…Babel 會將其轉換為 ES5 版本:

[2, 4, 6].map(function(n) {
return n * 2;
});

什麼是轉譯 PHP?

Web 開發中潛在的新事物是轉換伺服器端代碼的可能性,尤其是 PHP。

轉譯 PHP 的工作方式與轉譯 JavaScript 的方式相同:現代 PHP 版本的源代碼被轉換為舊 PHP 版本的等效代碼。

按照與之前相同的示例,來自 PHP 7.4 的箭頭函數:

$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);

…可以轉換成其等效的 PHP 7.3 版本:

$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)
{
// …
}

如果錯誤仍然在生產中發生,則拋出的錯誤消息將不如我們有聯合類型時那麼精確。然而,首先能夠使用聯合類型克服了這種潛在的缺點。

0 在我們所有的網站上發布,並在新版本發布後立即更新 😌 但情況並非總是如此。在此處了解有關轉譯 PHP 代碼的所有信息👇點擊推文

轉譯 PHP 代碼的優勢

轉譯使人們能夠使用最新版本的 PHP 編寫應用程序,並生成一個也可以在運行舊版本 PHP 的環境中運行的版本。

這對於為遺留內容管理系統 (CMS) 創建產品的開發人員特別有用。例如,WordPress 仍然正式支持 PHP 5.6(即使它推薦 PHP 7.4+)。運行 PHP 5.6 到 7.2 版本的 WordPress 站點的百分比——它們都是生命周期終止 (EOL),這意味著它們不再接收安全更新——高達 34.8%,而那些運行在任何 PHP 版本之外的 PHP 版本8.0 高達 99.5%:

WordPress 版本使用情況

按版本劃分的 WordPress 使用統計數據。圖片來源:WordPress

因此,針對全球受眾的 WordPress 主題和插件很可能會使用舊版本的 PHP 進行編碼,以增加其可能的覆蓋範圍。多虧了轉譯,這些可以使用 PHP 8.0 進行編碼,並且仍會針對較舊的 PHP 版本發布,從而針對儘可能多的用戶。

事實上,任何需要支持除最新版本以外的任何 PHP 版本(即使在當前支持的 PHP 版本範圍內)的應用程序都可以受益。

Drupal 就是這種情況,它需要 PHP 7.3。由於轉譯,開發人員可以使用 PHP 8.0 創建公開可用的 Drupal 模塊,並使用 PHP 7.3 發布它們。

另一個示例是為由於某種原因而無法在其環境中運行 PHP 8.0 的客戶創建自定義代碼時。儘管如此,多虧了轉譯,開發人員仍然可以使用 PHP 8.0 編寫他們的可交付成果,並在這些遺留環境中運行它們。

何時轉譯 PHP

PHP 代碼始終可以被轉譯,除非它包含某些 PHP 功能,而這些功能在以前的 PHP 版本中是沒有的。

PHP 8.0 中引入的屬性可能就是這種情況:

#[SomeAttr]
函數 someFunc() {}

#[AnotherAttr]
class SomeClass {}

在前面使用箭頭函數的例子中,代碼可以被轉譯,因為箭頭函數是語法糖。相比之下,屬性會創建全新的行為。這種行為也可以在 PHP 7.4 及以下版本中重現,但只能通過手動編碼,即不能根據工具或流程自動進行(AI 可以提供解決方案,但我們還沒有)。

可以像刪除聯合類型一樣刪除用於開發用途的屬性,例如 #[Deprecated]。但是在生產中修改應用程序行為的屬性不能刪除,也不能直接轉換。

截至今天,沒有任何轉譯器可以採用具有 PHP 8.0 屬性的代碼並自動生成其等效的 PHP 7.4 代碼。因此,如果您的 PHP 代碼需要使用屬性,那麼對其進行轉譯將是困難的或不可行的。

可以轉譯的 PHP 特性

這些是 PHP 7.1 及更高版本的功能,目前可以轉譯。如果您的代碼只使用這些功能,您可以確信您的轉譯應用程序將正常工作。否則,您需要評估轉譯的代碼是否會產生故障。

PHP版本 特徵
7.1 一切
7.2 – 對象類型
– 參數類型擴展
– preg_match 中的 PREG_UNMATCHED_AS_NULL 標誌
7.3 – list() 中的引用賦值/數組解構(foreach 內部除外 – #4376)
– 靈活的 Heredoc 和 Nowdoc 語法
– 函數調用中的尾隨逗號
– set(raw)cookie 接受 $option 參數
7.4 – 類型化屬性
– 箭頭函數
– 空合併賦值運算符
– 在數組內解包
– 數字文字分隔符
– 帶有標籤名稱數組的 strip_tags()
– 協變返回類型和逆變參數類型
8.0 – 聯合類型
– 混合偽類型
– 靜態返回類型
– ::class 對象上的魔術常量
– 匹配表達式
– 僅按類型捕獲異常
– 空安全運算符
– 類構造函數屬性提升
– 參數列表和閉包使用列表中的尾隨逗號

PHP 轉譯器

目前,有一種用於轉譯 PHP 代碼的工具:Rector。

Rector 是一個 PHP 重構器工具,它根據可編程規則轉換 PHP 代碼。我們輸入源代碼和要運行的規則集,Rector 將轉換代碼。

Rector 通過命令行操作,通過 Composer 安裝在項目中。執行時,Rector 將輸出轉換前後代碼的「差異」(綠色添加,紅色刪除):

來自 Rector 的「diff」輸出

轉譯到哪個版本的 PHP

要跨 PHP 版本轉換代碼,必須創建相應的規則。

今天,Rector 庫包含了 PHP 8.0 到 7.1 範圍內的大部分代碼轉換規則。因此,我們可以可靠地將 PHP 代碼轉換為 7.1 版。

還有一些從 PHP 7.1 到 7.0 以及從 7.0 到 5.6 的轉換規則,但這些規則並不詳盡。完成它們的工作正在進行中,因此我們最終可能會將 PHP 代碼轉換為 5.6 版。

轉譯與向後移植

向後移植類似於轉譯,但更簡單。向後移植代碼不一定依賴於語言的新功能。相反,只需從新版本的語言中複製/粘貼/改編相應的代碼,就可以向舊版本的語言提供相同的功能。

例如,函數 str_contains 是在 PHP 8.0 中引入的。PHP 7.4 及以下版本的相同功能可以像這樣輕鬆實現:

if (!defined(‘PHP_VERSION_ID’) || (defined(‘PHP_VERSION_ID’) && PHP_VERSION_ID < 80000)) {
if (!function_exists(‘str_contains’)) {
/**
* 檢查字元串是否包含另一個
*
* @param string $haystack 要搜索的字元串
* @param string $needle 要搜索的字元串
* @return boolean 如果在 haystack 中找到了針,則返回 TRUE,否則返回 FALSE。
*/
function str_contains(string $haystack, string $needle): bool
{
return strpos($haystack, $needle) !== false;
}
}
}

因為向後移植比轉譯更簡單,所以只要向後移植完成工作,我們就應該選擇這種解決方案。

關於 PHP 8.0 到 7.1 之間的範圍,我們可以使用 Symfony 的 polyfill 庫:

  • 填充 PHP 7.1
  • 填充 PHP 7.2
  • 填充 PHP 7.3
  • 填充 PHP 7.4
  • 填充 PHP 8.0

這些庫向後移植以下函數、類、常量和介面:

PHP版本 特徵
7.2 職能:

  • spl_object_id
  • utf8_encode
  • utf8_decode

常數:

  • PHP_FLOAT_*
  • PHP_OS_FAMILY
7.3 職能:

  • array_key_first
  • array_key_last
  • 小時
  • is_countable

例外:

  • 異常
7.4 職能:

  • get_mangled_object_vars
  • mb_str_split
  • 密碼演算法
8.0 介面:

  • 可串接

課程:

  • 值錯誤
  • 未處理的匹配錯誤

常數:

  • FILTER_VALIDATE_BOOL

職能:

  • 股利
  • get_debug_type
  • preg_last_error_msg
  • str_contains
  • str_starts_with
  • str_ends_with
  • get_resource_id

轉換後的 PHP 示例

讓我們檢查一些已轉譯的 PHP 代碼示例,以及一些正在完全轉譯的包。

PHP代碼

匹配表達式是在 PHP 8.0 中引入的。此源代碼:

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

…將使用 switch 運算符轉換為等效的 PHP 7.4 版本:

function getFieldValue(string $fieldName): ?string
{
switch ($fieldName) {
case ‘foo’:
return ‘foofoo’;
case ‘bar’:
返回 ‘​​barbar’;
case ‘baz’:
返回 ‘​​bazbaz’;
默認值:
返回空;
}
}

PHP 8.0 中還引入了 nullsafe 運算符:

公共函數 getValue(TypeResolverInterface $typeResolver): ?string
{
return $this->getResolver($typeResolver)?->getValue();
}

轉譯後的代碼需要先將操作的值賦給一個新的變數,以避免兩次執行操作:

公共函數 getValue(TypeResolverInterface $typeResolver): ?string
{
return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null;
}

PHP 8.0 中也引入了構造函數屬性提升功能,允許開發人員編寫更少的代碼:

class QueryResolver
{
function __construct(protected QueryFormatter $queryFormatter)
{
}
}

在為 PHP 7.4 轉譯它時,會生成完整的代碼段:

類 QueryResolver
{
受保護的 QueryFormatter $queryFormatter;

函數 __construct(QueryFormatter $queryFormatter)
{
$this->queryFormatter = $queryFormatter;
}
}

上面轉譯的代碼包含類型化屬性,這些屬性是在 PHP 7.4 中引入的。將該代碼轉換為 PHP 7.3 會用 docblocks 替換它們:

class QueryResolver
{
/**
* @var QueryFormatter
*/
protected $queryFormatter;

函數 __construct(QueryFormatter $queryFormatter)
{
$this->queryFormatter = $queryFormatter;
}
}

PHP 包

正在為生產轉譯以下庫:

庫/描述 代碼/注釋
使轉譯成為可能的Rector PHP 重建器工具 – 源代碼
– 轉譯的代碼
– 注釋
使 PHP 代碼遵守一組規則的簡易編碼標準工具 – 源代碼
– 轉譯的代碼
– 注釋
GraphQL API for WordPress
Plugin為 WordPress提供 GraphQL 伺服器
– 源代碼
– 轉譯的代碼
– 注釋

轉譯 PHP 的優缺點

已經描述了轉譯 PHP 的好處:它允許源代碼使用 PHP 8.0(即 PHP 的最新版本),該版本將被轉換為 PHP 的較低版本,用於生產在遺留應用程序或環境中運行。

這有效地使我們能夠成為更好的開發人員,生成更高質量的代碼。這是因為我們的源代碼可以使用 PHP 8.0 的聯合類型、PHP 7.4 的類型化屬性,以及添加到每個新版本 PHP(PHP 8.0 的混合,PHP 7.2 的對象)中的不同類型和偽類型,其中PHP 的其他現代特性。

使用這些特性,我們可以更好地捕捉開發過程中的錯誤並編寫更易於閱讀的代碼。

現在,讓我們來看看缺點。

它必須被編碼和維護

Rector 可以自動轉換代碼,但該過程可能需要一些手動輸入才能使其與我們的特定設置配合使用。

第三方庫也必須被轉譯

每當轉換它們產生錯誤時,這就會成為一個問題,因為我們必須深入研究它們的源代碼以找出可能的原因。如果問題可以解決並且項目是開源的,我們將需要提交拉取請求。如果庫不是開源的,我們可能會遇到障礙。

當代碼無法轉譯時,Rector 不會通知我們

如果源代碼包含 PHP 8.0 屬性或任何其他無法轉譯的功能,我們將無法繼續。但是,Rector 不會檢查這種情況,因此我們需要手動進行檢查。這對於我們自己的源代碼來說可能不是一個大問題,因為我們已經熟悉它,但它可能成為第三方依賴的障礙。

調試信息使用轉譯後的代碼,而不是源代碼

當應用程序在生產中產生帶有堆棧跟蹤的錯誤消息時,行號將指向轉譯後的代碼。我們需要將轉譯後的代碼轉換回原始代碼,才能在源代碼中找到對應的行號。

轉譯的代碼也必須加前綴

我們轉譯的項目和其他一些也安裝在生產環境中的庫可以使用相同的第三方依賴項。此第三方依賴項將為我們的項目轉譯,並為其他庫保留其原始源代碼。因此,轉譯後的版本必須通過 PHP-Scoper、Strauss 或其他一些工具作為前綴,以避免潛在的衝突。

訂閱時事通訊

想知道我們是如何將流量增加超過 1000% 的嗎?

加入 20,000 多名其他人,他們會收到我們的每周時事通訊,其中包含 WordPress 內幕技巧!

現在訂閱

轉換必須在持續集成 (CI) 期間進行

因為轉譯的代碼自然會覆蓋源代碼,所以我們不應該在我們的開發計算機上運行轉譯過程,否則我們會冒產生副作用的風險。在 CI 運行期間運行該過程更合適(更多內容見下文)。

如何轉譯 PHP

首先,我們需要在我們的項目中安裝Rector進行開發:

作曲家需要校長/校長 –dev

然後我們在項目的根目錄中創建一個 rector.php 配置文件,其中包含所需的規則集。要將代碼從 PHP 8.0 降級到 7.1,我們使用以下配置:

使用 RectorSetValueObjectDowngradeSetList;
使用 SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator;

返回靜態函數 (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->import(DowngradeSetList::PHP_80);
$containerConfigurator->import(DowngradeSetList::PHP_74);
$containerConfigurator->import(DowngradeSetList::PHP_73);
$containerConfigurator->import(DowngradeSetList::PHP_72);
};

為了確保進程按預期執行,我們可以在乾燥模式下運行 Rector 的進程命令,傳遞要處理的位置(在這種情況下,文件夾 src/ 下的所有文件):

供應商/bin/rector 進程 src –dry-run

為了執行轉譯,我們運行 Rector 的 process 命令,它將修改現有位置中的文件:

供應商/bin/rector 進程 src

請注意:如果我們在開發計算機上運行 rector 進程,源代碼將在 src/ 下就地轉換。但是,我們希望在不同的位置生成轉換後的代碼,以免在降級代碼時覆蓋源代碼。因此,在持續集成期間運行流程最合適。

優化轉譯過程

要為生產生成轉譯的可交付成果,只需轉換生產代碼;可以跳過僅用於開發的代碼。這意味著我們可以避免轉譯所有測試(對於我們的項目及其依賴項)和所有開發依賴項。

關於測試,我們已經知道我們項目的測試所在的位置——例如,在文件夾 tests/ 下。我們還必須找出依賴項的位置——例如,在它們的子文件夾 test/、test/ 和 Test/(針對不同的庫)下。然後,我們告訴 Rector 跳過處理這些文件夾:

返回靜態函數 (ContainerConfigurator $containerConfigurator): void {
// …

$parameters->set(Option::SKIP, [
// 跳過測試
‘*/tests/*’,
‘*/test/*’,
‘*/Test/*’,
]);
};

關於依賴,Composer 知道哪些是用於開發的(那些在 composer.json 中的 require-dev 條目下),哪些是用於生產的(那些在條目 require 下的)。

要從 Composer 檢索所有生產依賴項的路徑,我們運行:

作曲家信息 –path –no-dev

此命令將生成一個包含名稱和路徑的依賴項列表,如下所示:

大腦/皮質/Users/leo/GitHub/leoloso/PoP/vendor/brain/cortex
composer/installers /Users/leo/GitHub/leoloso/PoP/vendor/composer/installers
composer/semver /Users/leo/GitHub/leoloso/ PoP/vendor/composer/semver
guzzlehttp /guzzle /Users/leo/GitHub/leoloso/PoP/vendor/guzzlehttp/guzzle League
/pipeline /Users/leo/GitHub/leoloso/PoP/vendor/league/pipeline

我們可以提取所有路徑並將它們提供給 Rector 命令,然後該命令將處理我們項目的 src/ 文件夾以及包含所有生產依賴項的文件夾:

$path=”$(composer info –path –no-dev | cut -d’ ‘ -f2- | sed ‘s/ //g’ | tr ‘n’ ‘ ‘)”
$ vendor/bin/rector process源 $paths

進一步的改進可以防止 Rector 處理那些已經使用目標 PHP 版本的依賴項。如果庫已使用 PHP 7.1(或以下任何版本)編碼,則無需將其轉換為 PHP 7.1。

為此,我們可以獲取需要 PHP 7.2 及更高版本的庫列表並僅處理這些庫。我們將通過 Composer 的為什麼不命令獲取所有這些庫的名稱,如下所示:

作曲家為什麼不 php “7.1.*” | grep -o “S*/S*”

由於此命令不適用於 –no-dev 標誌,為了僅包含生產依賴項,我們首先需要刪除開發依賴項並重新生成自動載入器,執行該命令,然後再次添加它們:

$ composer install –no-dev
$ packages=$(composer why-not php “7.1.*” | grep -o “S*/S*”)
$ composer install

Composer 的 info –path 命令檢索包的路徑,格式如下:

# 執行這個命令
$ composer info psr/cache –path
# 產生這個響應:psr
/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache

我們對列表中的所有項目執行此命令以獲取所有轉譯路徑:

對於 $packages 中的包
do
path=$(composer info $package –path | cut -d’ ‘ -f2-)
paths=”$paths $path”
done

最後,我們將此列表提供給 Rector(加上項目的 src/ 文件夾):

需要一個為您提供競爭優勢的託管解決方案?Kinsta 為您提供令人難以置信的速度、最先進的安全性和自動縮放功能。查看我們的計劃

供應商/bin/rector 進程 src $paths

轉譯代碼時要避免的陷阱

轉譯代碼可以被視為一門藝術,通常需要針對項目進行調整。讓我們看看我們可能會遇到的一些問題。

鏈式規則並不總是被處理

鏈式規則是指規則需要轉換先前規則生成的代碼。

例如,庫 symfony/cache 包含以下代碼:

final class CacheItem 實現 ItemInterface
{
public function tag($tags): ItemInterface
{
// …
return $this;
}
}

當從 PHP 7.4 轉譯到 7.3 時,函數標籤必須經過兩個修改:

  • 由於規則 DowngradeCovariantReturnTypeRector,返回類型 ItemInterface 必須首先轉換為 self
  • 由於規則 DowngradeSelfTypeDeclarationRector,然後必須刪除返回類型 self

最終結果應該是這樣的:

final class CacheItem 實現了 ItemInterface
{
public function tag($tags)
{
// …
return $this;
}
}

但是,Rector 只輸出中間階段:

final class CacheItem 實現了 ItemInterface
{
public function tag($tags): self
{
// …
return $this;
}
}

問題是 Rector 無法始終控制應用規則的順序。

解決方案是確定哪些鏈式規則未處理,並執行新的 Rector 運行以應用它們。

為了識別鏈式規則,我們在源代碼上運行了兩次 Rector,如下所示:

$ vendor/bin/rector 進程src
$ vendor/bin/rector 進程src –dry-run

第一次,我們按預期運行 Rector,以執行轉譯。第二次,我們使用 –dry-run 標誌來發現是否還有要進行的更改。如果存在,該命令將退出並顯示錯誤代碼,「diff」輸出將指示仍可應用哪些規則。這意味著第一次運行沒有完成,一些鏈式規則沒有被處理。

使用 --dry-run 標誌運行 Rector

使用 –dry-run 標誌運行 Rector

一旦我們確定了未應用的鏈式規則(或規則),我們就可以創建另一個 Rector 配置文件——例如,rector-chained-rule.php 將執行缺失的規則。這一次,我們可以在需要應用的特定文件上運行特定的缺失規則,而不是為 src/ 下的所有文件處理完整的規則集:

// rector-chained-rule.php
使用 RectorCoreConfigurationOption;
使用 RectorDowngradePhp74RectorClassMethodDowngradeSelfTypeDeclarationRector;
使用 SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator;

返回靜態函數(ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(DowngradeSelfTypeDeclarationRector::class);

$parameters = $containerConfigurator->parameters();
$parameters->set(Option::
PATHS , [ __DIR__ . ‘/vendor/symfony/cache/CacheItem.php’,
]);
};

最後,我們告訴 Rector 在第二遍通過輸入 –config 使用新的配置文件:

# 首先通過所有修改
$ vendor/bin/rector process src

# 第二遍修復特定問題
$ vendor/bin/rector process –config=rector-chained-rule.php

Composer 依賴項可能不一致

庫可以聲明一個要開發的依賴項(即在 composer.json 中的 require-dev 下),但仍然引用它們中的一些代碼用於生產(例如在 src/ 下的某些文件,而不是 tests/ 下)。

通常,這不是問題,因為該代碼可能不會在生產中載入,因此應用程序上永遠不會出現錯誤。但是,當 Rector 處理源代碼及其依賴項時,它會驗證所有引用的代碼都可以載入。如果任何文件引用來自未安裝庫的某些代碼段,Rector 將拋出錯誤(因為它被聲明為僅用於開發)。

例如,Symfony 的 Cache 組件中的 EarlyExpirationHandler 類實現了 Messenger 組件中的 MessageHandlerInterface 介面:

class EarlyExpirationHandler 實現 MessageHandlerInterface
{
//…
}

然而,symfony/cache 聲明 symfony/messenger 是開發的依賴。然後,當在依賴 symfony/cache 的項目上運行 Rector 時,它會拋出一個錯誤:

[錯誤] 無法處理「vendor/symfony/cache/Messenger/EarlyExpirationHandler.php」文件,原因是:
「分析錯誤:未找到類 SymfonyComponentMessengerHandlerMessageHandlerInterface。」。將您的文件包含在「$parameters->set(Option:: AUTOLOAD_PATHS, […]);” 在 “rector.php” 配置中。
見 https://github.com/rectorphp/rector#configuration”。

這個問題有以下三種解決方案:

  1. 在 Rector 配置中,跳過處理引用該代碼段的文件:

返回靜態函數 (ContainerConfigurator $containerConfigurator): void {
// …

$parameters->set(Option::SKIP, [
__DIR__ . ‘/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php’,
]);
};

  1. 下載缺少的庫並添加其路徑以供 Rector 自動載入:

返回靜態函數 (ContainerConfigurator $containerConfigurator): void {
// …

$parameters->set(Option::AUTOLOAD_PATHS, [
__DIR__ . ‘/vendor/symfony/messenger’,
]);
};

  1. 讓您的項目依賴於缺少的生產庫:

作曲家需要 symfony/messenger

轉譯和持續集成

如前所述,在我們的開發計算機中,我們必須在運行 Rector 時使用 –dry-run 標誌,否則,源代碼將被轉譯的代碼覆蓋。因此,更適合在持續集成 (CI) 期間運行實際的轉譯過程,我們可以在其中啟動臨時運行器來執行該過程。

執行轉譯過程的理想時間是為我們的項目生成版本。例如,下面的代碼是 GitHub Actions 的工作流程,它創建了 WordPress 插件的發布:

名稱:生成可安裝插件並上傳為發布資產

發布:
類型:[已發布]
工作:
構建:
名稱:構建、降級和上傳發布
運行:ubuntu-最新
步驟:
-名稱:結帳代碼
使用:動作/[電子郵件受保護]
– 名稱:用於生產的降級代碼(到 PHP 7.1)
運行:|
composer install
vendor/bin/rector process
sed -i ‘s/Requires PHP: 7.4/Requires PHP: 7.1/’ graphql-api.php
– name: Build project for production
run: |
composer install –no-dev –optimize-autoloader
mkdir build
– name: Create artifact
uses: montudor/[email protected]
with:
參數: zip -X -r build/graphql-api.zip 。-x *.git* node_modules/* .* “*/.*” CODE_OF_CONDUCT.md CONTRIBUTING.md ISSUE_TEMPLATE.md PULL_REQUEST_TEMPLATE.md rector.php *.dist composer.* dev-helpers** build**
– name: Upload工件
使用:動作/[電子郵件保護]
與:
名稱:graphql-api
路徑:build/graphql-api.zip
– 名稱:上傳到發布
用途:JasonEtco/[電子郵件保護]
與:
args:build/graphql-api.zip應用程序/zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

此工作流程包含通過 GitHub 操作發布 WordPress 插件的標準程序。將插件的代碼從 PHP 7.4 轉換為 7.1 的新增功能發生在以下步驟中:

– 名稱:用於生產的降級代碼(到 PHP 7.1)
運行:|
vendor/bin/rector process
sed -i ‘s/Requires PHP: 7.4/Requires PHP: 7.1/’ graphql-api.php

總的來說,此工作流現在執行以下步驟:

  1. 從使用 PHP 7.4 編寫的存儲庫中查看 WordPress 插件的源代碼
  2. 安裝其 Composer 依賴項
  3. 將其代碼從 PHP 7.4 轉換為 7.1
  4. 將插件主文件頭中的「Requires PHP」條目從「7.4」修改為「7.1」
  5. 刪除開發所需的依賴項
  6. 創建插件的 .zip 文件,排除所有不需要的文件
  7. 將 .zip 文件作為發布資產上傳(此外,作為 GitHub 操作的工件)

測試轉換後的代碼

一旦代碼被轉換為 PHP 7.1,我們怎麼知道它運行良好?或者,換句話說,我們怎麼知道它已經被徹底轉換了,並且沒有留下任何更高版本的 PHP 代碼的殘餘?

與轉譯代碼類似,我們可以在 CI 過程中實現解決方案。這個想法是使用 PHP 7.1 設置運行器的環境並在轉換後的代碼上運行 linter。如果有任何代碼段與 PHP 7.1 不兼容(例如未轉換的 PHP 7.4 類型化屬性),則 linter 將拋出錯誤。

適用於 PHP 的 linter 是 PHP Parallel Lint。我們可以將此庫安裝為項目中的開發依賴項,或者讓 CI 進程將其安裝為獨立的 Composer 項目:

作曲家創建項目 php-parallel-lint/php-parallel-lint

每當代碼包含 PHP 7.2 及更高版本時,PHP Parallel Lint 將拋出如下錯誤:

運行 php-parallel-lint/parallel-lint layers/ vendor/ –exclude vendor/symfony/polyfill-ctype/bootstrap80.php –exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php –exclude vendor/symfony /polyfill-intl-idn/bootstrap80.php –exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php –exclude vendor/symfony/polyfill-mbstring/bootstrap80.php
PHP 7.1.33 | 10 個並行作業
………………………………. …… 60/2870 (2 %)
…………………. …………………………2870分之120(4%)

………. ………………………………………………………………………………………………………………………………………………………… 660/2870 (22 %)
………………………….X………………………….. ………………. 720/2870 (25 %)
…………………………………………………… 780/2870 (27 %)

…………………………………………………… 2820/2870 (98 %)
………………………………………….. 2870/2870 (100 %)

在 15.4 秒內檢查了 2870 個文件
在 1 個文件中發現了語法錯誤

————————————————– ———-
解析錯誤:layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php:55
53| ‘0.8.0’,
54| __(‘GraphQL API for WordPress’, ‘graphql-api’),
> 55| ))) {
56| $plugin->setup();
57| }
在層意外’)’/ GraphQLAPIForWP /插件/ graphql-API換WP上線55 / graphql-api.php
錯誤:過程,退出代碼1完成。

讓我們將 linter 添加到 CI 的工作流程中。執行將代碼從 PHP 8.0 轉譯到 7.1 並對其進行測試的步驟是:

  1. 查看源代碼
  2. 讓環境運行PHP 8.0,以便Rector可以解釋源代碼
  3. 將代碼轉換為 PHP 7.1
  4. 安裝 PHP linter 工具
  5. 切換環境的PHP版本為7.1
  6. 在轉譯的代碼上運行 linter

這個 GitHub Action 工作流程完成了這項工作:

名稱:降級 PHP 測試
作業:
主要:
名稱:通過 Rector 將代碼降級到 PHP 7.1,並執行測試
運行:ubuntu-最新
步驟:
-名稱:結帳代碼
使用:動作/[電子郵件保護]

– 名稱:設置 PHP
使用:shivammathur/[email protected]
with:
php-version:8.0
覆蓋範圍:none

– 名稱:本地包 – 通過 Rector
運行降級 PHP 代碼: |
composer 安裝
vendor/bin/rector 進程

# 準備在 PHP 7.1 上進行測試
– 名稱:安裝 PHP Parallel Lint
運行:composer create-project php-parallel-lint/php-parallel-lint –ansi

– 名稱:切換到 PHP 7.1
使用:shivammathur/[email protected]
with:
php-version:7.1
coverage:none

# Lint 轉換後的代碼
– name: Run PHP Parallel Lint on PHP 7.1
run: php-parallel-lint/parallel-lint src/ vendor/ –exclude vendor/symfony/polyfill-ctype/bootstrap80.php –exclude vendor/symfony /polyfill-intl-grapheme/bootstrap80.php –exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php –exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php –exclude vendor/symfony/polyfill -mbstring/bootstrap80.php

請注意,來自 Symfony 的 polyfill 庫(不需要轉譯)的幾個 bootstrap80.php 文件必須從 linter 中排除。這些文件包含 PHP 8.0,因此 linter 在處理它們時會拋出錯誤。但是,排除這些文件是安全的,因為只有在運行 PHP 8.0 或更高版本時才會在生產中載入它們:

if (PHP_VERSION_ID >= 80000) {
return require __DIR__.’/bootstrap80.php’;
}

無論您是為 WordPress 創建公共插件還是更新舊代碼,有很多原因可能無法使用最新的 PHP 版本 👩‍💻 在本指南中了解轉譯如何提供幫助 👇點擊推文

概括

這篇文章教我們如何轉譯我們的 PHP 代碼,允許我們在源代碼中使用 PHP 8.0 並創建一個適用於 PHP 7.1 的版本。轉譯是通過一個 PHP 重構工具 Rector 完成的。

轉譯我們的代碼使我們成為更好的開發人員,因為我們可以更好地捕捉開發中的錯誤並生成自然更易於閱讀和理解的代碼。

轉譯還使我們能夠將具有特定 PHP 要求的代碼與 CMS 分離。如果我們希望使用最新版本的 PHP 來創建公開可用的 WordPress 插件或 Drupal 模塊,而不會嚴重限制我們的用戶群,我們現在可以這樣做。

您對轉換 PHP 有任何疑問嗎?請在評價部分留下您的意見!

通過以下方式節省時間、成本並最大限度地提高站點性能:

  • 來自 WordPress 託管專家的即時幫助,24/7。
  • Cloudflare 企業集成。
  • 全球受眾覆蓋全球 28 個數據中心。
  • 使用我們內置的應用程序性能監控進行優化。

所有這些以及更多,都在一個沒有長期合同、協助遷移和 30 天退款保證的計劃中。查看我們的計劃或與銷售人員交談以找到適合您的計劃。

相關文章