日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
跨版本PHP代碼轉(zhuǎn)換終極教程

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項目有:域名注冊、雅安服務(wù)器托管、營銷軟件、網(wǎng)站建設(shè)、金川網(wǎng)站維護、網(wǎng)站推廣。

在理想情況下,我們應(yīng)該為我們的所有網(wǎng)站使用php8.0(撰寫本文時的最新版本),并在新版本發(fā)布后立即進行更新。但是,開發(fā)人員通常需要使用以前的PHP版本,例如為WordPress創(chuàng)建公共插件或使用妨礙升級web服務(wù)器環(huán)境的遺留代碼時。

在這種情況下,我們可能會放棄使用最新PHP代碼的希望。但是還有一個更好的選擇:我們?nèi)匀豢梢允褂肞HP8.0編寫源代碼,并將其轉(zhuǎn)換到以前的PHP版本,甚至是PHP7.1。

在本指南中,我們將教您有關(guān)轉(zhuǎn)換PHP代碼的所有知識。

什么是Transpiling?

Transpiling將源代碼從編程語言轉(zhuǎn)換為相同或不同編程語言的等效源代碼。

Transpiling并不是Web開發(fā)中的一個新概念:客戶端開發(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ù),因為它們是語法糖,即生成現(xiàn)有行為的新語法。這是低垂的果實。

然而,也有一些新特性創(chuàng)建了一種新的行為,因此,對于以前版本的PHP不會有等效的代碼。PHP 8.0中引入的聯(lián)合類型就是這樣:

function someFunction(float|int $param): string|float|int|null
{
  // ...
}

在這些情況下,只要開發(fā)需要新特性,而不是生產(chǎn)需要新特性,就仍然可以進行轉(zhuǎn)換。然后,我們可以簡單地從轉(zhuǎn)換的代碼中完全刪除該特性,而不會產(chǎn)生嚴(yán)重后果。

聯(lián)合類型就是這樣一個例子。此功能用于檢查輸入類型與其提供的值之間是否不匹配,這有助于防止錯誤。如果與類型發(fā)生沖突,那么開發(fā)中就會出現(xiàn)錯誤,我們應(yīng)該在代碼到達生產(chǎn)環(huán)境之前捕獲并修復(fù)它。

因此,我們可以從生產(chǎn)代碼中刪除該功能:

function someFunction($param)
{
  // ...
}

如果錯誤仍然發(fā)生在生產(chǎn)中,拋出的錯誤消息將不如使用聯(lián)合類型時準(zhǔn)確。然而,這一潛在的缺點被能夠首先使用聯(lián)合類型所抵消。

Transpiling PHP的優(yōu)勢

Transpiling使您能夠使用最新版本的PHP編寫應(yīng)用程序,并生成一個在運行舊版本PHP的環(huán)境中也能工作的版本。

這對于為舊式內(nèi)容管理系統(tǒng)(CMS)創(chuàng)建產(chǎn)品的開發(fā)人員特別有用。例如,WordPress仍然官方支持PHP5.6(盡管它推薦PHP7.4+)。運行PHP版本5.6到7.2的WordPress站點的百分比為34.8%,而運行PHP版本(8.0除外)的站點的百分比高達99.5%:

WordPress使用情況統(tǒng)計(按版本)圖像來源:WordPress

因此,面向全球受眾的WordPress主題和插件很可能使用舊版本的PHP進行編碼,以增加其可能的影響范圍。多虧了transpiling,這些代碼可以使用PHP8.0進行編碼,并且仍然可以針對較舊的PHP版本發(fā)布,從而盡可能多地面向用戶。

事實上,任何需要支持除最新版本以外的任何PHP版本(即使在當(dāng)前支持的PHP版本范圍內(nèi))的應(yīng)用程序都可以從中受益。

Drupal就是這樣,它需要PHP7.3。由于transpiling,開發(fā)人員可以使用PHP8.0創(chuàng)建公開可用的Drupal模塊,并使用PHP7.3發(fā)布它們。

另一個例子是為由于某種原因而無法在其環(huán)境中運行PHP8.0的客戶機創(chuàng)建自定義代碼時。盡管如此,多虧了transpiling,開發(fā)人員仍然可以使用PHP8.0編寫可交付成果,并在這些遺留環(huán)境中運行它們。

何時需要轉(zhuǎn)換PHP(Transpile PHP)

PHP代碼始終可以轉(zhuǎn)換,除非它包含一些在之前的PHP版本中沒有對等的PHP功能。

情況可能就是這樣屬性,在PHP 8.0中介紹:

#[SomeAttr]
function someFunc() {}

#[AnotherAttr]
class SomeClass {}

在前面使用箭頭函數(shù)的示例中,可以轉(zhuǎn)換代碼,因為箭頭函數(shù)是語法糖。相反,屬性創(chuàng)建了全新的行為。PHP7.4及以下版本也可以復(fù)制這種行為,但只能通過手動編碼,即不自動基于工具或流程(AI可以提供解決方案,但我們還沒有)。

用于開發(fā)的屬性,如#[Deprecated],可以用刪除聯(lián)合類型的相同方式刪除。但是,不能刪除在生產(chǎn)中修改應(yīng)用程序行為的屬性,也不能直接轉(zhuǎn)換這些屬性。

到目前為止,沒有一個transpiler能夠接受具有PHP8.0屬性的代碼并自動生成其等效PHP7.4代碼。因此,如果您的PHP代碼需要使用屬性,那么轉(zhuǎn)換它將是困難的或不可行的。

可轉(zhuǎn)換PHP功能

這些是PHP7.1及以上版本的特性,目前可以轉(zhuǎn)換。如果您的代碼只使用這些特性,那么您可以確信您的轉(zhuǎn)換應(yīng)用程序?qū)⒄9ぷ鳌7駝t,您將需要評估轉(zhuǎn)換的代碼是否會產(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 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轉(zhuǎn)換器(PHP Transpilers)

目前,有一個用于轉(zhuǎn)換PHP代碼的工具:Rector。

Rector是一個PHP重構(gòu)工具,它根據(jù)可編程規(guī)則轉(zhuǎn)換PHP代碼。我們輸入源代碼和要運行的規(guī)則集,Rector將轉(zhuǎn)換代碼。

Rector通過命令行操作,通過Composer安裝在項目中。執(zhí)行時,Rector將在轉(zhuǎn)換前后輸出代碼的“diff”(添加為綠色,刪除為紅色):

來自Rector的“diff”輸出

轉(zhuǎn)換到哪一個PHP版本

要跨PHP版本轉(zhuǎn)換代碼,必須創(chuàng)建相應(yīng)的規(guī)則。

今天,Rector庫包含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ī)則并不詳盡。完成這些代碼的工作正在進行中,因此我們可能最終會將PHP代碼轉(zhuǎn)換到5.6版。

Transpiling vs Backporting

Backporting與Transpiling類似,但更簡單。Backporting代碼不一定依賴于語言的新特性。相反,只需從新版本的語言復(fù)制/粘貼/改編相應(yīng)的代碼,就可以為舊版本的語言提供相同的功能。

例如,PHP 8.0中引入了str_contains函數(shù)。PHP 7.4及以下版本的相同功能可以像這樣輕松實現(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;
    }
  }
}

因為Backporting比Transpiling更簡單,所以無論何時進行Backporting,我們都應(yīng)該選擇這種解決方案。

關(guān)于PHP8.0到7.1之間的范圍,我們可以使用Symfony的polyfill庫:

  • Polyfill PHP 7.1
  • Polyfill PHP 7.2
  • Polyfill PHP 7.3
  • Polyfill PHP 7.4
  • Polyfill PHP 8.0

這些庫支持以下函數(shù)、類、常量和接口:

PHP 版本 特征
7.2 功能:

  • spl_object_id
  • utf8_encode
  • utf8_decode

函數(shù):

  • PHP_FLOAT_*
  • PHP_OS_FAMILY
7.3 功能:

  • array_key_first
  • array_key_last
  • hrtime
  • is_countable

異常處理:

  • JsonException
7.4 功能:

  • get_mangled_object_vars
  • mb_str_split
  • password_algos
8.0 接口:

  • Stringable

類:

  • ValueError
  • UnhandledMatchError

函數(shù):

  • FILTER_VALIDATE_BOOL

功能:

  • fdiv
  • get_debug_type
  • preg_last_error_msg
  • str_contains
  • str_starts_with
  • str_ends_with
  • get_resource_id

Transpiled PHP示例

讓我們一起來看看幾個轉(zhuǎn)換PHP代碼的示例,以及幾個正在完全轉(zhuǎn)換的包。

PHP代碼

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

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

…將使用switch運算符轉(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)換時,會生成完整的代碼:

 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包

以下庫正在轉(zhuǎn)換以用于生產(chǎn):

庫/描述 代碼/注釋
Rector
PHP重建工具,使轉(zhuǎn)換成為可能
–源代碼
–已轉(zhuǎn)換代碼
–筆記
易于編碼標(biāo)準(zhǔn)
具有PHP代碼的工具遵守一組規(guī)則
–源代碼
–已轉(zhuǎn)換代碼
–筆記
GraphQL API for WordPress
一個為WordPress提供GraphQL server的插件
–源代碼
–已轉(zhuǎn)換代碼
–筆記

轉(zhuǎn)換PHP的利弊

轉(zhuǎn)換PHP的好處已經(jīng)描述過了:它允許源代碼使用PHP 8.0(即PHP的最新版本),PHP將被轉(zhuǎn)換為較低版本,以便在遺留應(yīng)用程序或環(huán)境中運行。

這有效地讓我們成為更好的開發(fā)人員,生成更高質(zhì)量的代碼。這是因為我們的源代碼可以使用PHP8.0的聯(lián)合類型、PHP7.4的類型屬性,以及添加到每個新版本PHP中的不同類型和偽類型(混合自PHP8.0,對象來自PHP7.2),以及PHP的其他現(xiàn)代功能。

使用這些特性,我們可以在開發(fā)過程中更好地捕獲bug,并編寫更易于閱讀的代碼。

現(xiàn)在,讓我們看看缺點。

必須對它進行編碼和維護

Rector可以自動轉(zhuǎn)換代碼,但這個過程可能需要一些手動輸入,使其與我們特定的設(shè)置配合使用。

第三方庫也必須轉(zhuǎn)換

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

Rector不會通知我們代碼何時不能轉(zhuǎn)錄

如果源代碼包含PHP8.0屬性或任何其他無法轉(zhuǎn)換的功能,我們將無法繼續(xù)。但是,Rector不會檢查此條件,因此我們需要手動執(zhí)行此操作。這對于我們自己的源代碼來說可能不是一個大問題,因為我們已經(jīng)熟悉它了,但它可能成為第三方依賴關(guān)系的障礙。

調(diào)試信息使用轉(zhuǎn)換代碼,而不是源代碼

當(dāng)應(yīng)用程序在生產(chǎn)中生成帶有堆棧跟蹤的錯誤消息時,行號將指向轉(zhuǎn)換的代碼。我們需要將轉(zhuǎn)換的代碼轉(zhuǎn)換回原始代碼,以便在源代碼中找到相應(yīng)的行號。

還必須對已轉(zhuǎn)換的代碼進行預(yù)綴

我們的轉(zhuǎn)樁項目和其他一些庫也安裝在生產(chǎn)環(huán)境中,可以使用相同的第三方依賴性。此第三方依賴將轉(zhuǎn)入我們的項目,并保留其其他庫的原始源代碼。因此,轉(zhuǎn)錄的版本必須通過PHP-范圍,斯特勞斯,或一些其他工具,以避免潛在的沖突。

我們的Transpile項目和其他一些也安裝在生產(chǎn)環(huán)境中的庫可以使用相同的第三方依賴關(guān)系。這個第三方依賴項將為我們的項目轉(zhuǎn)換,并為其他庫保留其原始源代碼。因此,必須通過PHP Scope、Strauss或其他工具為轉(zhuǎn)換版本添加前綴,以避免潛在沖突。

在連續(xù)集成 (CI) 期間必須進行轉(zhuǎn)換

因為轉(zhuǎn)換的代碼自然會覆蓋源代碼,所以我們不應(yīng)該在開發(fā)計算機上運行轉(zhuǎn)換過程,否則我們將冒產(chǎn)生副作用的風(fēng)險。在CI運行期間運行該進程更合適(下面將對此進行詳細介紹)。

如何轉(zhuǎn)換PHP(Transpile PHP)

首先,我們需要在開發(fā)項目中安裝Rector:

composer require rector/rector --dev

然后,我們在項目的根目錄中創(chuàng)建一個rector.php配置文件,其中包含所需的規(guī)則集。要將代碼從PHP 8.0降級到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下運行Rector的process命令,傳遞要處理的位置(在本例中,是src/文件夾下的所有文件):

vendor/bin/rector process src --dry-run

要執(zhí)行轉(zhuǎn)換,我們運行Rector的process命令,該命令將修改現(xiàn)有位置內(nèi)的文件:

vendor/bin/rector process src

請注意:如果我們在開發(fā)計算機中運行rector process,源代碼將在src/下進行轉(zhuǎn)換。但是,我們希望在不同的位置生成轉(zhuǎn)換后的代碼,以便在降級代碼時不會覆蓋源代碼。因此,在持續(xù)集成期間運行流程最合適。

優(yōu)化轉(zhuǎn)換過程

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

關(guān)于測試,我們已經(jīng)知道項目的測試在哪里了——例如,在tests/文件夾下。我們還必須找出依賴項的位置——例如,在它們的子文件夾tests/test/Test/(針對不同的庫)下。然后,我們告訴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)的所有依賴項的路徑,我們運行:

composer info --path --no-dev

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

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命令中,然后該命令將處理項目的src/文件夾以及包含所有生產(chǎn)依賴項的文件夾:

$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')"
$ vendor/bin/rector process src $paths

進一步的改進可以防止Rector處理那些已經(jīng)使用目標(biāo)PHP版本的依賴項。如果一個庫是用PHP7.1(或下面的任何版本)編寫的,那么就不需要將它轉(zhuǎn)換到PHP7.1。

為了實現(xiàn)這一點,我們可以獲得需要PHP7.2及以上版本的庫列表,并只處理這些庫。我們將通過Composer的why-not命令獲得所有這些庫的名稱,如下所示:

composer why-not php "7.1.*" | grep -o "\S*\/\S*"

由于此命令不適用于--no-dev標(biāo)志,為了只包含生產(chǎn)依賴項,我們首先需要刪除開發(fā)依賴項并重新生成自動加載程序,執(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

我們對列表中的所有項目執(zhí)行此命令,以獲取要轉(zhuǎn)換的所有路徑:

for package in $packages
do
  path=$(composer info $package --path | cut -d' ' -f2-)
  paths="$paths $path"
done

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

vendor/bin/rector process src $paths

轉(zhuǎn)換代碼時需要避免的坑

轉(zhuǎn)換代碼可以被視為一門藝術(shù),通常需要針對項目進行調(diào)整。讓我們看看我們可能遇到的一些問題。

鏈?zhǔn)揭?guī)則(Chained Rules)并不總是被處理的

鏈?zhǔn)揭?guī)則是指規(guī)則需要轉(zhuǎn)換前一個規(guī)則生成的代碼。

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

final class CacheItem implements ItemInterface
{
  public function tag($tags): ItemInterface
  {
    // ...
    return $this;
  }
}

從PHP 7.4轉(zhuǎn)換到7.3時,函數(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īng)用它們。

為了識別鏈?zhǔn)揭?guī)則,我們在源代碼上運行了兩次Rector,如下所示:

$ vendor/bin/rector process src
$ vendor/bin/rector process src --dry-run

第一次,我們按預(yù)期運行Rector,以執(zhí)行轉(zhuǎn)換。第二次,我們使用--dry-run標(biāo)志來發(fā)現(xiàn)是否還有需要進行的更改。如果有,命令將退出并顯示錯誤代碼,“diff”輸出將指示仍可以應(yīng)用哪些規(guī)則。這意味著第一次運行未完成,某些鏈?zhǔn)揭?guī)則未被處理。

使用–dry-run flag運行Rector

一旦我們確定了未應(yīng)用的鏈?zhǔn)揭?guī)則,我們就可以創(chuàng)建另一個目錄配置文件——例如,rector-chained-rule.php 將執(zhí)行缺少的規(guī)則。這一次,我們不需要為 src/下的所有文件處理一整套規(guī)則,而是可以在需要應(yīng)用該規(guī)則的特定文件上運行特定的缺失規(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',
  ]);
};

最后,我們在第二次傳遞時通過輸入--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依賴性可能不一致

庫可以聲明一個依賴項進行開發(fā)(即在composer.json中的require-dev 下),但仍然可以引用其中的一些代碼進行生產(chǎn)(例如在 src/下的一些文件上,而不是在 tests/ 下)。

通常,這不是問題,因為該代碼可能不會加載到生產(chǎn)環(huán)境中,因此應(yīng)用程序上永遠不會出現(xiàn)錯誤。但是,當(dāng)Rector處理源代碼及其依賴項時,它會驗證是否可以加載所有引用的代碼。如果任何文件引用了未安裝庫中的某段代碼(因為它被聲明為僅用于開發(fā)),Rector將拋出一個錯誤。

例如,Symfony緩存組件中的類 EarlyExpirationHandler 實現(xiàn)Messenger組件中的接口 MessageHandlerInterface :

class EarlyExpirationHandler implements MessageHandlerInterface
{
    //...
}

但是,symfony/cache聲明 symfony/messenger是開發(fā)的依賴項。然后,在依賴symfony/cache的項目上運行Rector時,它將拋出一個錯誤:

[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".

這個問題有三種解決辦法:

轉(zhuǎn)換和連續(xù)集成

如前所述,在我們的開發(fā)計算機中,在運行Rector時必須使用 --dry-run 標(biāo)志,否則,源代碼將被轉(zhuǎn)換的代碼覆蓋。因此,更適合在持續(xù)集成(CI)期間運行實際的轉(zhuǎn)換過程,我們可以啟動臨時運行程序來執(zhí)行該過程。

執(zhí)行轉(zhuǎn)換過程的理想時間是為我們的項目生成發(fā)布時。例如,下面的代碼是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/action-zip@v0.1.0
        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í)行以下步驟:

測試轉(zhuǎn)換代碼

一旦代碼被轉(zhuǎn)換到 PHP 7.1,我們怎么知道它工作得很好?或者,換句話說,我們怎么知道它已經(jīng)被徹底轉(zhuǎn)換,并且沒有留下更高版本的 PHP 代碼的殘余?

與轉(zhuǎn)存代碼類似,我們可以在 CI 流程中實現(xiàn)解決方案。這個想法是設(shè)置與PHP 7.1的亞軍的環(huán)境,并在轉(zhuǎn)樁代碼上運行一個襯墊。如果任何代碼與 PHP 7.1 不兼容(例如未轉(zhuǎn)換的 PHP 7.4 中的鍵入屬性),則襯里會拋出錯誤。

Php 的襯里效果很好PHP 平行林特.我們可以將此庫安裝為項目開發(fā)的依賴項,或者讓 CI 過程將其安裝為獨立的作曲家項目:

一旦代碼被轉(zhuǎn)換到PHP7.1,我們?nèi)绾沃浪ぷ髁己??或者,換句話說,我們?nèi)绾沃浪呀?jīng)被徹底轉(zhuǎn)換,并且沒有留下更高版本的PHP代碼的殘余?

與轉(zhuǎn)換代碼類似,我們可以在CI流程中實現(xiàn)解決方案。其想法是使用PHP7.1設(shè)置運行者環(huán)境,并在轉(zhuǎn)換的代碼上運行l(wèi)inter。如果任何代碼與PHP7.1不兼容(例如PHP7.4中未轉(zhuǎn)換的類型化屬性),那么linter將拋出一個錯誤。

PHP的一個運行良好的linter是PHP并行Lint。我們可以將此庫作為開發(fā)依賴項安裝在我們的項目中,或者讓CI流程將其作為獨立的Composer項目安裝:

composer create-project php-parallel-lint/php-parallel-lint

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

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 并測試它的步驟是:

這GitHub 行動工作流程做這項工作:

讓我們將linter添加到CI的工作流中。將代碼從PHP 8.0轉(zhuǎn)換到7.1并進行測試的步驟如下:

此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

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

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

小結(jié)

本文教我們?nèi)绾无D(zhuǎn)換PHP代碼,允許我們在源代碼中使用PHP8.0,并創(chuàng)建一個適用于PHP7.1的發(fā)行版。Transpiling是通過Rector(一種PHP重構(gòu)工具)完成的。

轉(zhuǎn)換代碼使開發(fā)人員的功效效率更高,因為我們可以更好地捕獲開發(fā)中的錯誤,并生成自然更易于閱讀和理解的代碼。

Transpiling還使我們能夠?qū)⒕哂刑囟≒HP需求的代碼與CMS分離。如果我們希望使用最新版本的PHP來創(chuàng)建公開可用的WordPress插件或Drupal模塊,而不嚴(yán)重限制我們的用戶群,那么我們現(xiàn)在可以這樣做。


網(wǎng)頁標(biāo)題:跨版本PHP代碼轉(zhuǎn)換終極教程
文章源于:http://www.5511xx.com/article/coidhes.html