新聞中心

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡(jiǎn)單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、雅安服務(wù)器托管、營(yíng)銷軟件、網(wǎng)站建設(shè)、金川網(wǎng)站維護(hù)、網(wǎng)站推廣。
在理想情況下,我們應(yīng)該為我們的所有網(wǎng)站使用php8.0(撰寫本文時(shí)的最新版本),并在新版本發(fā)布后立即進(jìn)行更新。但是,開發(fā)人員通常需要使用以前的PHP版本,例如為WordPress創(chuàng)建公共插件或使用妨礙升級(jí)web服務(wù)器環(huán)境的遺留代碼時(shí)。
在這種情況下,我們可能會(huì)放棄使用最新PHP代碼的希望。但是還有一個(gè)更好的選擇:我們?nèi)匀豢梢允褂肞HP8.0編寫源代碼,并將其轉(zhuǎn)換到以前的PHP版本,甚至是PHP7.1。
在本指南中,我們將教您有關(guān)轉(zhuǎn)換PHP代碼的所有知識(shí)。
什么是Transpiling?
Transpiling將源代碼從編程語言轉(zhuǎn)換為相同或不同編程語言的等效源代碼。
Transpiling并不是Web開發(fā)中的一個(gè)新概念:客戶端開發(fā)人員很可能熟悉Babel,JavaScript代碼的轉(zhuǎn)換器。
Babel將現(xiàn)代ECMAScript 2015+版本中的JavaScript代碼轉(zhuǎn)換為與舊瀏覽器兼容的舊版本。例如,給定ES2015箭頭函數(shù):
[2, 4, 6].map((n) => n * 2);
…Babel將其轉(zhuǎn)換為ES5版本:
[2, 4, 6].map(function(n) {
return n * 2;
});
什么是Transpiling PHP?
Web開發(fā)中潛在的新功能是轉(zhuǎn)換服務(wù)器端代碼的可能性,特別是PHP。
轉(zhuǎn)換PHP的工作方式與轉(zhuǎn)換JavaScript的工作方式相同:現(xiàn)代PHP版本的源代碼轉(zhuǎn)換為舊PHP版本的等效代碼。
下面是與前面相同的示例,PHP 7.4中的箭頭函數(shù):
$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);
…可以轉(zhuǎn)換為其等效的PHP 7.3版本:
$nums = array_map(
function ($n) {
return $n * 2;
},
[2, 4, 6]
);
可以轉(zhuǎn)換箭頭函數(shù),因?yàn)樗鼈兪钦Z法糖,即生成現(xiàn)有行為的新語法。這是低垂的果實(shí)。
然而,也有一些新特性創(chuàng)建了一種新的行為,因此,對(duì)于以前版本的PHP不會(huì)有等效的代碼。PHP 8.0中引入的聯(lián)合類型就是這樣:
function someFunction(float|int $param): string|float|int|null
{
// ...
}
在這些情況下,只要開發(fā)需要新特性,而不是生產(chǎn)需要新特性,就仍然可以進(jìn)行轉(zhuǎn)換。然后,我們可以簡(jiǎn)單地從轉(zhuǎn)換的代碼中完全刪除該特性,而不會(huì)產(chǎn)生嚴(yán)重后果。
聯(lián)合類型就是這樣一個(gè)例子。此功能用于檢查輸入類型與其提供的值之間是否不匹配,這有助于防止錯(cuò)誤。如果與類型發(fā)生沖突,那么開發(fā)中就會(huì)出現(xiàn)錯(cuò)誤,我們應(yīng)該在代碼到達(dá)生產(chǎn)環(huán)境之前捕獲并修復(fù)它。
因此,我們可以從生產(chǎn)代碼中刪除該功能:
function someFunction($param)
{
// ...
}
如果錯(cuò)誤仍然發(fā)生在生產(chǎn)中,拋出的錯(cuò)誤消息將不如使用聯(lián)合類型時(shí)準(zhǔn)確。然而,這一潛在的缺點(diǎn)被能夠首先使用聯(lián)合類型所抵消。
Transpiling PHP的優(yōu)勢(shì)
Transpiling使您能夠使用最新版本的PHP編寫應(yīng)用程序,并生成一個(gè)在運(yùn)行舊版本PHP的環(huán)境中也能工作的版本。
這對(duì)于為舊式內(nèi)容管理系統(tǒng)(CMS)創(chuàng)建產(chǎn)品的開發(fā)人員特別有用。例如,WordPress仍然官方支持PHP5.6(盡管它推薦PHP7.4+)。運(yùn)行PHP版本5.6到7.2的WordPress站點(diǎn)的百分比為34.8%,而運(yùn)行PHP版本(8.0除外)的站點(diǎn)的百分比高達(dá)99.5%:
WordPress使用情況統(tǒng)計(jì)(按版本)圖像來源:WordPress
因此,面向全球受眾的WordPress主題和插件很可能使用舊版本的PHP進(jìn)行編碼,以增加其可能的影響范圍。多虧了transpiling,這些代碼可以使用PHP8.0進(jìn)行編碼,并且仍然可以針對(duì)較舊的PHP版本發(fā)布,從而盡可能多地面向用戶。
事實(shí)上,任何需要支持除最新版本以外的任何PHP版本(即使在當(dāng)前支持的PHP版本范圍內(nèi))的應(yīng)用程序都可以從中受益。
Drupal就是這樣,它需要PHP7.3。由于transpiling,開發(fā)人員可以使用PHP8.0創(chuàng)建公開可用的Drupal模塊,并使用PHP7.3發(fā)布它們。
另一個(gè)例子是為由于某種原因而無法在其環(huán)境中運(yùn)行PHP8.0的客戶機(jī)創(chuàng)建自定義代碼時(shí)。盡管如此,多虧了transpiling,開發(fā)人員仍然可以使用PHP8.0編寫可交付成果,并在這些遺留環(huán)境中運(yùn)行它們。
何時(shí)需要轉(zhuǎn)換PHP(Transpile PHP)
PHP代碼始終可以轉(zhuǎn)換,除非它包含一些在之前的PHP版本中沒有對(duì)等的PHP功能。
情況可能就是這樣屬性,在PHP 8.0中介紹:
#[SomeAttr]
function someFunc() {}
#[AnotherAttr]
class SomeClass {}
在前面使用箭頭函數(shù)的示例中,可以轉(zhuǎn)換代碼,因?yàn)榧^函數(shù)是語法糖。相反,屬性創(chuàng)建了全新的行為。PHP7.4及以下版本也可以復(fù)制這種行為,但只能通過手動(dòng)編碼,即不自動(dòng)基于工具或流程(AI可以提供解決方案,但我們還沒有)。
用于開發(fā)的屬性,如#[Deprecated],可以用刪除聯(lián)合類型的相同方式刪除。但是,不能刪除在生產(chǎn)中修改應(yīng)用程序行為的屬性,也不能直接轉(zhuǎn)換這些屬性。
到目前為止,沒有一個(gè)transpiler能夠接受具有PHP8.0屬性的代碼并自動(dòng)生成其等效PHP7.4代碼。因此,如果您的PHP代碼需要使用屬性,那么轉(zhuǎn)換它將是困難的或不可行的。
可轉(zhuǎn)換PHP功能
這些是PHP7.1及以上版本的特性,目前可以轉(zhuǎn)換。如果您的代碼只使用這些特性,那么您可以確信您的轉(zhuǎn)換應(yīng)用程序?qū)⒄9ぷ鳌7駝t,您將需要評(píng)估轉(zhuǎn)換的代碼是否會(huì)產(chǎn)生故障。
| 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 typesmixed pseudo typestatic return type::class magic constant on objectsmatch expressionscatch exceptions only by typeNull-safe operatorClass constructor property promotionTrailing commas in parameter lists and closure use lists |
PHP轉(zhuǎn)換器(PHP Transpilers)
目前,有一個(gè)用于轉(zhuǎn)換PHP代碼的工具:Rector。
Rector是一個(gè)PHP重構(gòu)工具,它根據(jù)可編程規(guī)則轉(zhuǎn)換PHP代碼。我們輸入源代碼和要運(yùn)行的規(guī)則集,Rector將轉(zhuǎn)換代碼。
Rector通過命令行操作,通過Composer安裝在項(xiàng)目中。執(zhí)行時(shí),Rector將在轉(zhuǎn)換前后輸出代碼的“diff”(添加為綠色,刪除為紅色):
來自Rector的“diff”輸出
轉(zhuǎn)換到哪一個(gè)PHP版本
要跨PHP版本轉(zhuǎn)換代碼,必須創(chuàng)建相應(yīng)的規(guī)則。
今天,Rector庫(kù)包含PHP8.0到7.1范圍內(nèi)的大多數(shù)代碼轉(zhuǎn)換規(guī)則。因此,我們可以可靠地將PHP代碼轉(zhuǎn)換到7.1版。
也有從PHP7.1到7.0以及從7.0到5.6的轉(zhuǎn)換規(guī)則,但這些規(guī)則并不詳盡。完成這些代碼的工作正在進(jìn)行中,因此我們可能最終會(huì)將PHP代碼轉(zhuǎn)換到5.6版。
Transpiling vs Backporting
Backporting與Transpiling類似,但更簡(jiǎn)單。Backporting代碼不一定依賴于語言的新特性。相反,只需從新版本的語言復(fù)制/粘貼/改編相應(yīng)的代碼,就可以為舊版本的語言提供相同的功能。
例如,PHP 8.0中引入了str_contains函數(shù)。PHP 7.4及以下版本的相同功能可以像這樣輕松實(shí)現(xiàn):
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;
}
}
}
因?yàn)锽ackporting比Transpiling更簡(jiǎn)單,所以無論何時(shí)進(jìn)行Backporting,我們都應(yīng)該選擇這種解決方案。
關(guān)于PHP8.0到7.1之間的范圍,我們可以使用Symfony的polyfill庫(kù):
- Polyfill PHP 7.1
- Polyfill PHP 7.2
- Polyfill PHP 7.3
- Polyfill PHP 7.4
- Polyfill PHP 8.0
這些庫(kù)支持以下函數(shù)、類、常量和接口:
| PHP 版本 | 特征 |
|---|---|
| 7.2 | 功能:
函數(shù):
|
| 7.3 | 功能:
異常處理:
|
| 7.4 | 功能:
|
| 8.0 | 接口:
類:
函數(shù):
功能:
|
Transpiled PHP示例
讓我們一起來看看幾個(gè)轉(zhuǎn)換PHP代碼的示例,以及幾個(gè)正在完全轉(zhuǎn)換的包。
PHP代碼
match 表達(dá)式是在PHP8.0中引入的。此源代碼:
function getFieldValue(string $fieldName): ?string
{
return match($fieldName) {
'foo' => 'foofoo',
'bar' => 'barbar',
'baz' => 'bazbaz',
default => null,
};
}
…將使用switch運(yùn)算符轉(zhuǎn)換到其等效的PHP 7.4版本:
function getFieldValue(string $fieldName): ?string
{
switch ($fieldName) {
case 'foo':
return 'foofoo';
case 'bar':
return 'barbar';
case 'baz':
return 'bazbaz';
default:
return null;
}
}
PHP 8.0中還引入了nullsafe操作符:
public function getValue(TypeResolverInterface $typeResolver): ?string
{
return $this->getResolver($typeResolver)?->getValue();
}
轉(zhuǎn)換的代碼需要首先將操作的值分配給新變量,以避免執(zhí)行兩次操作:
public function getValue(TypeResolverInterface $typeResolver): ?string
{
return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null;
}
PHP 8.0中還引入了構(gòu)造函數(shù)屬性提升功能,允許開發(fā)人員編寫更少的代碼:
class QueryResolver
{
function __construct(protected QueryFormatter $queryFormatter)
{
}
}
在為PHP7.4轉(zhuǎn)換時(shí),會(huì)生成完整的代碼:
class QueryResolver
{
protected QueryFormatter $queryFormatter;
function __construct(QueryFormatter $queryFormatter)
{
$this->queryFormatter = $queryFormatter;
}
}
上面轉(zhuǎn)換的代碼包含PHP7.4中引入的類型化屬性。將代碼向下轉(zhuǎn)換到PHP7.3將其替換為docblocks:
class QueryResolver
{
/**
* @var QueryFormatter
*/
protected $queryFormatter;
function __construct(QueryFormatter $queryFormatter)
{
$this->queryFormatter = $queryFormatter;
}
}
PHP包
以下庫(kù)正在轉(zhuǎn)換以用于生產(chǎn):
| 庫(kù)/描述 | 代碼/注釋 |
|---|---|
| Rector PHP重建工具,使轉(zhuǎn)換成為可能 |
–源代碼 –已轉(zhuǎn)換代碼 –筆記 |
| 易于編碼標(biāo)準(zhǔn) 具有PHP代碼的工具遵守一組規(guī)則 |
–源代碼 –已轉(zhuǎn)換代碼 –筆記 |
| GraphQL API for WordPress 一個(gè)為WordPress提供GraphQL server的插件 |
–源代碼 –已轉(zhuǎn)換代碼 –筆記 |
轉(zhuǎn)換PHP的利弊
轉(zhuǎn)換PHP的好處已經(jīng)描述過了:它允許源代碼使用PHP 8.0(即PHP的最新版本),PHP將被轉(zhuǎn)換為較低版本,以便在遺留應(yīng)用程序或環(huán)境中運(yùn)行。
這有效地讓我們成為更好的開發(fā)人員,生成更高質(zhì)量的代碼。這是因?yàn)槲覀兊脑创a可以使用PHP8.0的聯(lián)合類型、PHP7.4的類型屬性,以及添加到每個(gè)新版本PHP中的不同類型和偽類型(混合自PHP8.0,對(duì)象來自PHP7.2),以及PHP的其他現(xiàn)代功能。
使用這些特性,我們可以在開發(fā)過程中更好地捕獲bug,并編寫更易于閱讀的代碼。
現(xiàn)在,讓我們看看缺點(diǎn)。
必須對(duì)它進(jìn)行編碼和維護(hù)
Rector可以自動(dòng)轉(zhuǎn)換代碼,但這個(gè)過程可能需要一些手動(dòng)輸入,使其與我們特定的設(shè)置配合使用。
第三方庫(kù)也必須轉(zhuǎn)換
每當(dāng)轉(zhuǎn)換它們產(chǎn)生錯(cuò)誤時(shí),這就成為一個(gè)問題,因?yàn)槲覀儽仨毶钊胙芯克鼈兊脑创a以找出可能的原因。如果問題可以解決并且項(xiàng)目是開源的,我們將需要提交一個(gè)pull請(qǐng)求。如果庫(kù)不是開源的,我們可能會(huì)遇到障礙。
Rector不會(huì)通知我們代碼何時(shí)不能轉(zhuǎn)錄
如果源代碼包含PHP8.0屬性或任何其他無法轉(zhuǎn)換的功能,我們將無法繼續(xù)。但是,Rector不會(huì)檢查此條件,因此我們需要手動(dòng)執(zhí)行此操作。這對(duì)于我們自己的源代碼來說可能不是一個(gè)大問題,因?yàn)槲覀円呀?jīng)熟悉它了,但它可能成為第三方依賴關(guān)系的障礙。
調(diào)試信息使用轉(zhuǎn)換代碼,而不是源代碼
當(dāng)應(yīng)用程序在生產(chǎn)中生成帶有堆棧跟蹤的錯(cuò)誤消息時(shí),行號(hào)將指向轉(zhuǎn)換的代碼。我們需要將轉(zhuǎn)換的代碼轉(zhuǎn)換回原始代碼,以便在源代碼中找到相應(yīng)的行號(hào)。
還必須對(duì)已轉(zhuǎn)換的代碼進(jìn)行預(yù)綴
我們的轉(zhuǎn)樁項(xiàng)目和其他一些庫(kù)也安裝在生產(chǎn)環(huán)境中,可以使用相同的第三方依賴性。此第三方依賴將轉(zhuǎn)入我們的項(xiàng)目,并保留其其他庫(kù)的原始源代碼。因此,轉(zhuǎn)錄的版本必須通過PHP-范圍,斯特勞斯,或一些其他工具,以避免潛在的沖突。
我們的Transpile項(xiàng)目和其他一些也安裝在生產(chǎn)環(huán)境中的庫(kù)可以使用相同的第三方依賴關(guān)系。這個(gè)第三方依賴項(xiàng)將為我們的項(xiàng)目轉(zhuǎn)換,并為其他庫(kù)保留其原始源代碼。因此,必須通過PHP Scope、Strauss或其他工具為轉(zhuǎn)換版本添加前綴,以避免潛在沖突。
在連續(xù)集成 (CI) 期間必須進(jìn)行轉(zhuǎn)換
因?yàn)檗D(zhuǎn)換的代碼自然會(huì)覆蓋源代碼,所以我們不應(yīng)該在開發(fā)計(jì)算機(jī)上運(yùn)行轉(zhuǎn)換過程,否則我們將冒產(chǎn)生副作用的風(fēng)險(xiǎn)。在CI運(yùn)行期間運(yùn)行該進(jìn)程更合適(下面將對(duì)此進(jìn)行詳細(xì)介紹)。
如何轉(zhuǎn)換PHP(Transpile PHP)
首先,我們需要在開發(fā)項(xiàng)目中安裝Rector:
composer require rector/rector --dev
然后,我們?cè)陧?xiàng)目的根目錄中創(chuàng)建一個(gè)rector.php配置文件,其中包含所需的規(guī)則集。要將代碼從PHP 8.0降級(jí)到7.1,我們使用以下配置:
use Rector\Set\ValueObject\DowngradeSetList;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->import(DowngradeSetList::PHP_80);
$containerConfigurator->import(DowngradeSetList::PHP_74);
$containerConfigurator->import(DowngradeSetList::PHP_73);
$containerConfigurator->import(DowngradeSetList::PHP_72);
};
為了確保流程按預(yù)期執(zhí)行,我們可以在dry mode下運(yùn)行Rector的process命令,傳遞要處理的位置(在本例中,是src/文件夾下的所有文件):
vendor/bin/rector process src --dry-run
要執(zhí)行轉(zhuǎn)換,我們運(yùn)行Rector的process命令,該命令將修改現(xiàn)有位置內(nèi)的文件:
vendor/bin/rector process src
請(qǐng)注意:如果我們?cè)陂_發(fā)計(jì)算機(jī)中運(yùn)行rector process,源代碼將在src/下進(jìn)行轉(zhuǎn)換。但是,我們希望在不同的位置生成轉(zhuǎn)換后的代碼,以便在降級(jí)代碼時(shí)不會(huì)覆蓋源代碼。因此,在持續(xù)集成期間運(yùn)行流程最合適。
優(yōu)化轉(zhuǎn)換過程
要生成用于生產(chǎn)的轉(zhuǎn)換可交付成果,只需轉(zhuǎn)換用于生產(chǎn)的代碼;可以跳過僅用于開發(fā)的代碼。這意味著我們可以避免轉(zhuǎn)換所有測(cè)試(對(duì)于我們的項(xiàng)目及其依賴項(xiàng))和所有開發(fā)依賴項(xiàng)。
關(guān)于測(cè)試,我們已經(jīng)知道項(xiàng)目的測(cè)試在哪里了——例如,在tests/文件夾下。我們還必須找出依賴項(xiàng)的位置——例如,在它們的子文件夾tests/、test/和Test/(針對(duì)不同的庫(kù))下。然后,我們告訴Rector跳過處理這些文件夾:
return static function (ContainerConfigurator $containerConfigurator): void {
// ...
$parameters->set(Option::SKIP, [
// Skip tests
'*/tests/*',
'*/test/*',
'*/Test/*',
]);
};
關(guān)于依賴關(guān)系,Composer知道哪些是用于開發(fā)的(條目下的需要composer.json中的 require-dev),哪些是用于生產(chǎn)的(條目下的require)。
要從Composer檢索生產(chǎn)的所有依賴項(xiàng)的路徑,我們運(yùn)行:
composer info --path --no-dev
此命令將生成包含其名稱和路徑的依賴項(xiàng)列表,如下所示:
brain/cortex /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命令中,然后該命令將處理項(xiàng)目的src/文件夾以及包含所有生產(chǎn)依賴項(xiàng)的文件夾:
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')" $ vendor/bin/rector process src $paths
進(jìn)一步的改進(jìn)可以防止Rector處理那些已經(jīng)使用目標(biāo)PHP版本的依賴項(xiàng)。如果一個(gè)庫(kù)是用PHP7.1(或下面的任何版本)編寫的,那么就不需要將它轉(zhuǎn)換到PHP7.1。
為了實(shí)現(xiàn)這一點(diǎn),我們可以獲得需要PHP7.2及以上版本的庫(kù)列表,并只處理這些庫(kù)。我們將通過Composer的why-not命令獲得所有這些庫(kù)的名稱,如下所示:
composer why-not php "7.1.*" | grep -o "\S*\/\S*"
由于此命令不適用于--no-dev標(biāo)志,為了只包含生產(chǎn)依賴項(xiàng),我們首先需要?jiǎng)h除開發(fā)依賴項(xiàng)并重新生成自動(dòng)加載程序,執(zhí)行該命令,然后再次添加它們:
$ composer install --no-dev $ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*") $ composer install
Composer的info --path命令檢索包的路徑,格式如下:
# Executing this command $ composer info psr/cache --path # Produces this response: psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache
我們對(duì)列表中的所有項(xiàng)目執(zhí)行此命令,以獲取要轉(zhuǎn)換的所有路徑:
for package in $packages do path=$(composer info $package --path | cut -d' ' -f2-) paths="$paths $path" done
最后,我們將此列表提供給Rector(加上項(xiàng)目的src/文件夾):
vendor/bin/rector process src $paths
轉(zhuǎn)換代碼時(shí)需要避免的坑
轉(zhuǎn)換代碼可以被視為一門藝術(shù),通常需要針對(duì)項(xiàng)目進(jìn)行調(diào)整。讓我們看看我們可能遇到的一些問題。
鏈?zhǔn)揭?guī)則(Chained Rules)并不總是被處理的
鏈?zhǔn)揭?guī)則是指規(guī)則需要轉(zhuǎn)換前一個(gè)規(guī)則生成的代碼。
例如,庫(kù)symfony/cache包含以下代碼:
final class CacheItem implements ItemInterface
{
public function tag($tags): ItemInterface
{
// ...
return $this;
}
}
從PHP 7.4轉(zhuǎn)換到7.3時(shí),函數(shù)標(biāo)記必須經(jīng)過兩次修改:
- 由于規(guī)則
DowngradeCovariantReturnTypeRector,返回類型ItemInterface必須首先轉(zhuǎn)換為self。 - 然后,由于規(guī)則
DowngradeSelfTypeDeclarationRector,必須刪除返回類型self。
最終結(jié)果應(yīng)該是:
final class CacheItem implements ItemInterface
{
public function tag($tags)
{
// ...
return $this;
}
}
但是,Rector只輸出中間階段:
final class CacheItem implements ItemInterface
{
public function tag($tags): self
{
// ...
return $this;
}
}
問題是,Rector不能始終控制規(guī)則的應(yīng)用順序。
解決方案是確定哪些鏈?zhǔn)揭?guī)則未被處理,并執(zhí)行新的目錄運(yùn)行以應(yīng)用它們。
為了識(shí)別鏈?zhǔn)揭?guī)則,我們?cè)谠创a上運(yùn)行了兩次Rector,如下所示:
$ vendor/bin/rector process src $ vendor/bin/rector process src --dry-run
第一次,我們按預(yù)期運(yùn)行Rector,以執(zhí)行轉(zhuǎn)換。第二次,我們使用--dry-run標(biāo)志來發(fā)現(xiàn)是否還有需要進(jìn)行的更改。如果有,命令將退出并顯示錯(cuò)誤代碼,“diff”輸出將指示仍可以應(yīng)用哪些規(guī)則。這意味著第一次運(yùn)行未完成,某些鏈?zhǔn)揭?guī)則未被處理。
使用–dry-run flag運(yùn)行Rector
一旦我們確定了未應(yīng)用的鏈?zhǔn)揭?guī)則,我們就可以創(chuàng)建另一個(gè)目錄配置文件——例如,rector-chained-rule.php 將執(zhí)行缺少的規(guī)則。這一次,我們不需要為 src/下的所有文件處理一整套規(guī)則,而是可以在需要應(yīng)用該規(guī)則的特定文件上運(yùn)行特定的缺失規(guī)則:
// rector-chained-rule.php
use Rector\Core\Configuration\Option;
use Rector\DowngradePhp74\Rector\ClassMethod\DowngradeSelfTypeDeclarationRector;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->set(DowngradeSelfTypeDeclarationRector::class);
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PATHS, [
__DIR__ . '/vendor/symfony/cache/CacheItem.php',
]);
};
最后,我們?cè)诘诙蝹鬟f時(shí)通過輸入--config告訴Rector使用新的配置文件:
# First pass with all modifications $ vendor/bin/rector process src # Second pass to fix a specific problem $ vendor/bin/rector process --config=rector-chained-rule.php
Composer依賴性可能不一致
庫(kù)可以聲明一個(gè)依賴項(xiàng)進(jìn)行開發(fā)(即在composer.json中的require-dev 下),但仍然可以引用其中的一些代碼進(jìn)行生產(chǎn)(例如在 src/下的一些文件上,而不是在 tests/ 下)。
通常,這不是問題,因?yàn)樵摯a可能不會(huì)加載到生產(chǎn)環(huán)境中,因此應(yīng)用程序上永遠(yuǎn)不會(huì)出現(xiàn)錯(cuò)誤。但是,當(dāng)Rector處理源代碼及其依賴項(xiàng)時(shí),它會(huì)驗(yàn)證是否可以加載所有引用的代碼。如果任何文件引用了未安裝庫(kù)中的某段代碼(因?yàn)樗宦暶鳛閮H用于開發(fā)),Rector將拋出一個(gè)錯(cuò)誤。
例如,Symfony緩存組件中的類 EarlyExpirationHandler 實(shí)現(xiàn)Messenger組件中的接口 MessageHandlerInterface :
class EarlyExpirationHandler implements MessageHandlerInterface
{
//...
}
但是,symfony/cache聲明 symfony/messenger是開發(fā)的依賴項(xiàng)。然后,在依賴symfony/cache的項(xiàng)目上運(yùn)行Rector時(shí),它將拋出一個(gè)錯(cuò)誤:
[ERROR] Could not process "vendor/symfony/cache/Messenger/EarlyExpirationHandler.php" file, due to: "Analyze error: "Class Symfony\Component\Messenger\Handler\MessageHandlerInterface not found.". Include your files in "$parameters->set(Option::AUTOLOAD_PATHS, [...]);" in "rector.php" config. See https://github.com/rectorphp/rector#configuration".
這個(gè)問題有三種解決辦法:
轉(zhuǎn)換和連續(xù)集成
如前所述,在我們的開發(fā)計(jì)算機(jī)中,在運(yùn)行Rector時(shí)必須使用 --dry-run 標(biāo)志,否則,源代碼將被轉(zhuǎn)換的代碼覆蓋。因此,更適合在持續(xù)集成(CI)期間運(yùn)行實(shí)際的轉(zhuǎn)換過程,我們可以啟動(dòng)臨時(shí)運(yùn)行程序來執(zhí)行該過程。
執(zhí)行轉(zhuǎn)換過程的理想時(shí)間是為我們的項(xiàng)目生成發(fā)布時(shí)。例如,下面的代碼是GitHub Actions的工作流,它創(chuàng)建了WordPress插件的發(fā)布:
name: Generate Installable Plugin and Upload as Release Asset
on:
release:
types: [published]
jobs:
build:
name: Build, Downgrade and Upload Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Downgrade code for production (to PHP 7.1)
run: |
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:
args: 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 artifact
uses: actions/upload-artifact@v2
with:
name: graphql-api
path: build/graphql-api.zip
- name: Upload to release
uses: JasonEtco/upload-to-release@master
with:
args: build/graphql-api.zip application/zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
此工作流包含通過GitHub操作發(fā)布WordPress插件的標(biāo)準(zhǔn)過程。新添加的將插件代碼從PHP7.4轉(zhuǎn)換到7.1的步驟如下:
- name: Downgrade code for production (to PHP 7.1)
run: |
vendor/bin/rector process
sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php
綜上所述,此工作流現(xiàn)在執(zhí)行以下步驟:
測(cè)試轉(zhuǎn)換代碼
一旦代碼被轉(zhuǎn)換到 PHP 7.1,我們?cè)趺粗浪ぷ鞯煤芎茫炕蛘?,換句話說,我們?cè)趺粗浪呀?jīng)被徹底轉(zhuǎn)換,并且沒有留下更高版本的 PHP 代碼的殘余?
與轉(zhuǎn)存代碼類似,我們可以在 CI 流程中實(shí)現(xiàn)解決方案。這個(gè)想法是設(shè)置與PHP 7.1的亞軍的環(huán)境,并在轉(zhuǎn)樁代碼上運(yùn)行一個(gè)襯墊。如果任何代碼與 PHP 7.1 不兼容(例如未轉(zhuǎn)換的 PHP 7.4 中的鍵入屬性),則襯里會(huì)拋出錯(cuò)誤。
Php 的襯里效果很好PHP 平行林特.我們可以將此庫(kù)安裝為項(xiàng)目開發(fā)的依賴項(xiàng),或者讓 CI 過程將其安裝為獨(dú)立的作曲家項(xiàng)目:
一旦代碼被轉(zhuǎn)換到PHP7.1,我們?nèi)绾沃浪ぷ髁己??或者,換句話說,我們?nèi)绾沃浪呀?jīng)被徹底轉(zhuǎn)換,并且沒有留下更高版本的PHP代碼的殘余?
與轉(zhuǎn)換代碼類似,我們可以在CI流程中實(shí)現(xiàn)解決方案。其想法是使用PHP7.1設(shè)置運(yùn)行者環(huán)境,并在轉(zhuǎn)換的代碼上運(yùn)行l(wèi)inter。如果任何代碼與PHP7.1不兼容(例如PHP7.4中未轉(zhuǎn)換的類型化屬性),那么linter將拋出一個(gè)錯(cuò)誤。
PHP的一個(gè)運(yùn)行良好的linter是PHP并行Lint。我們可以將此庫(kù)作為開發(fā)依賴項(xiàng)安裝在我們的項(xiàng)目中,或者讓CI流程將其作為獨(dú)立的Composer項(xiàng)目安裝:
composer create-project php-parallel-lint/php-parallel-lint
每當(dāng)代碼包含PHP 7.2及更高版本時(shí),PHP Parallel Lint將拋出如下錯(cuò)誤:
Run 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 parallel jobs
............................................................ 60/2870 (2 %)
............................................................ 120/2870 (4 %)
...
............................................................ 660/2870 (22 %)
.............X.............................................. 720/2870 (25 %)
............................................................ 780/2870 (27 %)
...
............................................................ 2820/2870 (98 %)
.................................................. 2870/2870 (100 %)
Checked 2870 files in 15.4 seconds
Syntax error found in 1 file
------------------------------------------------------------
Parse error: 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| }
Unexpected ')' in layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php on line 55
Error: Process completed with exit code 1.
讓我們將絨毛添加到我們的 CI 工作流中。執(zhí)行將代碼從 PHP 8.0 轉(zhuǎn)換到 7.1 并測(cè)試它的步驟是:
這GitHub 行動(dòng)工作流程做這項(xiàng)工作:
讓我們將linter添加到CI的工作流中。將代碼從PHP 8.0轉(zhuǎn)換到7.1并進(jìn)行測(cè)試的步驟如下:
此GitHub操作工作流執(zhí)行以下任務(wù):
name: Downgrade PHP tests
jobs:
main:
name: Downgrade code to PHP 7.1 via Rector, and execute tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set-up PHP
uses: shivammathur/setup-php@v2
with:
php-version: 8.0
coverage: none
- name: Local packages - Downgrade PHP code via Rector
run: |
composer install
vendor/bin/rector process
# Prepare for testing on PHP 7.1
- name: Install PHP Parallel Lint
run: composer create-project php-parallel-lint/php-parallel-lint --ansi
- name: Switch to PHP 7.1
uses: shivammathur/setup-php@v2
with:
php-version: 7.1
coverage: none
# Lint the transpiled code
- 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
請(qǐng)注意,必須從linter中排除Symfony的polyfill庫(kù)中的幾個(gè) bootstrap80.php 文件(不需要轉(zhuǎn)換)。這些文件包含PHP8.0,因此linter在處理它們時(shí)會(huì)拋出錯(cuò)誤。但是,排除這些文件是安全的,因?yàn)橹挥性谶\(yùn)行PHP 8.0或更高版本時(shí),才會(huì)在生產(chǎn)環(huán)境中加載這些文件:
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
小結(jié)
本文教我們?nèi)绾无D(zhuǎn)換PHP代碼,允許我們?cè)谠创a中使用PHP8.0,并創(chuàng)建一個(gè)適用于PHP7.1的發(fā)行版。Transpiling是通過Rector(一種PHP重構(gòu)工具)完成的。
轉(zhuǎn)換代碼使開發(fā)人員的功效效率更高,因?yàn)槲覀兛梢愿玫夭东@開發(fā)中的錯(cuò)誤,并生成自然更易于閱讀和理解的代碼。
Transpiling還使我們能夠?qū)⒕哂刑囟≒HP需求的代碼與CMS分離。如果我們希望使用最新版本的PHP來創(chuàng)建公開可用的WordPress插件或Drupal模塊,而不嚴(yán)重限制我們的用戶群,那么我們現(xiàn)在可以這樣做。
當(dāng)前名稱:跨版本PHP代碼轉(zhuǎn)換終極教程
鏈接分享:http://fisionsoft.com.cn/article/coidhes.html


咨詢
建站咨詢
