跨版本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箭頭函式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[2, 4, 6].map((n) => n * 2);
[2, 4, 6].map((n) => n * 2);
[2, 4, 6].map((n) => n * 2);

…Babel將其轉換為ES5版本:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[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中的箭頭函式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$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版本:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$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中引入的聯合型別就是這樣:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
{
  // ...
}

在這些情況下,只要開發需要新特性,而不是生產需要新特性,就仍然可以進行轉換。然後,我們可以簡單地從轉換的程式碼中完全刪除該特性,而不會產生嚴重後果。

聯合型別就是這樣一個例子。此功能用於檢查輸入型別與其提供的值之間是否不匹配,這有助於防止錯誤。如果與型別發生衝突,那麼開發中就會出現錯誤,我們應該在程式碼到達生產環境之前捕獲並修復它。

因此,我們可以從生產程式碼中刪除該功能:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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%:

WordPress使用情況統計(按版本)

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中介紹:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
#[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轉換器(PHP Transpilers)

目前,有一個用於轉換PHP程式碼的工具:Rector

Rector是一個PHP重構工具,它根據可程式設計規則轉換PHP程式碼。我們輸入原始碼和要執行的規則集,Rector將轉換程式碼。

Rector通過命令列操作,通過Composer安裝在專案中。執行時,Rector將在轉換前後輸出程式碼的“diff”(新增為綠色,刪除為紅色):

來自Rector的“diff”輸出

來自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及以下版本的相同功能可以像這樣輕鬆實現:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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中引入的。此原始碼:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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, }; }
function getFieldValue(string $fieldName): ?string
{
  return match($fieldName) {
    'foo' => 'foofoo',
    'bar' => 'barbar',
    'baz' => 'bazbaz',
    default => null,
  };
}

…將使用switch運算子轉換到其等效的PHP 7.4版本:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function getFieldValue(string $fieldName): ?string
{
switch ($fieldName) {
case 'foo':
return 'foofoo';
case 'bar':
return 'barbar';
case 'baz':
return 'bazbaz';
default:
return null;
}
}
function getFieldValue(string $fieldName): ?string { switch ($fieldName) { case 'foo': return 'foofoo'; case 'bar': return 'barbar'; case 'baz': return 'bazbaz'; default: return null; } }
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操作符:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public function getValue(TypeResolverInterface $typeResolver): ?string
{
return $this->getResolver($typeResolver)?->getValue();
}
public function getValue(TypeResolverInterface $typeResolver): ?string { return $this->getResolver($typeResolver)?->getValue(); }
public function getValue(TypeResolverInterface $typeResolver): ?string
{
  return $this->getResolver($typeResolver)?->getValue();
}

轉換的程式碼需要首先將操作的值分配給新變數,以避免執行兩次操作:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
public function getValue(TypeResolverInterface $typeResolver): ?string
{
return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null;
}
public function getValue(TypeResolverInterface $typeResolver): ?string { return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null; }
public function getValue(TypeResolverInterface $typeResolver): ?string
{
  return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null;
}

PHP 8.0中還引入了建構函式屬性提升功能,允許開發人員編寫更少的程式碼:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class QueryResolver
{
function __construct(protected QueryFormatter $queryFormatter)
{
}
}
class QueryResolver { function __construct(protected QueryFormatter $queryFormatter) { } }
class QueryResolver
{
  function __construct(protected QueryFormatter $queryFormatter)
  {
  }
}

在為PHP7.4轉換時,會生成完整的程式碼:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class QueryResolver
{
protected QueryFormatter $queryFormatter;
function __construct(QueryFormatter $queryFormatter)
{
$this->queryFormatter = $queryFormatter;
}
}
class QueryResolver { protected QueryFormatter $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
 class QueryResolver
 {
  protected QueryFormatter $queryFormatter;

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

上面轉換的程式碼包含PHP7.4中引入的型別化屬性。將程式碼向下轉換到PHP7.3將其替換為docblocks:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class QueryResolver
{
/**
* @var QueryFormatter
*/
protected $queryFormatter;
function __construct(QueryFormatter $queryFormatter)
{
$this->queryFormatter = $queryFormatter;
}
}
class QueryResolver { /** * @var QueryFormatter */ protected $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }
 class QueryResolver
 {
  /**
   * @var QueryFormatter
   */
  protected $queryFormatter;

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

PHP包

以下庫正在轉換以用於生產:

庫/描述 程式碼/註釋
Rector
PHP重建工具,使轉換成為可能
原始碼
已轉換程式碼
筆記
易於編碼標準
具有PHP程式碼的工具遵守一組規則
原始碼
已轉換程式碼
筆記
GraphQL API for WordPress
一個為WordPress提供GraphQL server的外掛
原始碼
已轉換程式碼
筆記

轉換PHP的利弊

轉換PHP的好處已經描述過了:它允許原始碼使用PHP 8.0(即PHP的最新版本),PHP將被轉換為較低版本,以便在遺留應用程式或環境中執行。

這有效地讓我們成為更好的開發人員,生成更高質量的程式碼。這是因為我們的原始碼可以使用PHP8.0的聯合型別、PHP7.4的型別屬性,以及新增到每個新版本PHP中的不同型別和偽型別(混合自PHP8.0,物件來自PHP7.2),以及PHP的其他現代功能。

使用這些特性,我們可以在開發過程中更好地捕獲bug,並編寫更易於閱讀的程式碼。

現在,讓我們看看缺點。

必須對它進行編碼和維護

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

第三方庫也必須轉換

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

Rector不會通知我們程式碼何時不能轉錄

如果原始碼包含PHP8.0屬性或任何其他無法轉換的功能,我們將無法繼續。但是,Rector不會檢查此條件,因此我們需要手動執行此操作。這對於我們自己的原始碼來說可能不是一個大問題,因為我們已經熟悉它了,但它可能成為第三方依賴關係的障礙。

除錯資訊使用轉換程式碼,而不是原始碼

當應用程式在生產中生成帶有堆疊跟蹤的錯誤訊息時,行號將指向轉換的程式碼。我們需要將轉換的程式碼轉換回原始程式碼,以便在原始碼中找到相應的行號。

還必須對已轉換的程式碼進行預綴

我們的轉樁專案和其他一些庫也安裝在生產環境中,可以使用相同的第三方依賴性。此第三方依賴將轉入我們的專案,並保留其其他庫的原始原始碼。因此,轉錄的版本必須通過PHP-範圍,斯特勞斯,或一些其他工具,以避免潛在的衝突。

我們的Transpile專案和其他一些也安裝在生產環境中的庫可以使用相同的第三方依賴關係。這個第三方依賴項將為我們的專案轉換,併為其他庫保留其原始原始碼。因此,必須通過PHP ScopeStrauss或其他工具為轉換版本新增字首,以避免潛在衝突。

在連續整合 (CI) 期間必須進行轉換

因為轉換的程式碼自然會覆蓋原始碼,所以我們不應該在開發計算機上執行轉換過程,否則我們將冒產生副作用的風險。在CI執行期間執行該程序更合適(下面將對此進行詳細介紹)。

如何轉換PHP(Transpile PHP)

首先,我們需要在開發專案中安裝Rector:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
composer require rector/rector --dev
composer require rector/rector --dev
composer require rector/rector --dev

然後,我們在專案的根目錄中建立一個rector.php配置檔案,其中包含所需的規則集。要將程式碼從PHP 8.0降級到7.1,我們使用以下配置:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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);
};
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); };
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);
};

為了確保流程按預期執行,我們可以在dry mode下執行Rector的process命令,傳遞要處理的位置(在本例中,是src/資料夾下的所有檔案):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
vendor/bin/rector process src --dry-run
vendor/bin/rector process src --dry-run
vendor/bin/rector process src --dry-run

要執行轉換,我們執行Rector的process命令,該命令將修改現有位置內的檔案:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
vendor/bin/rector process src
vendor/bin/rector process src
vendor/bin/rector process src

請注意:如果我們在開發計算機中執行rector process,原始碼將在src/下進行轉換。但是,我們希望在不同的位置生成轉換後的程式碼,以便在降級程式碼時不會覆蓋原始碼。因此,在持續整合期間執行流程最合適。

優化轉換過程

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

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
return static function (ContainerConfigurator $containerConfigurator): void {
// ...
$parameters->set(Option::SKIP, [
// Skip tests
'*/tests/*',
'*/test/*',
'*/Test/*',
]);
};
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ // Skip tests '*/tests/*', '*/test/*', '*/Test/*', ]); };
return static function (ContainerConfigurator $containerConfigurator): void {
  // ...

  $parameters->set(Option::SKIP, [
    // Skip tests
    '*/tests/*',
    '*/test/*',
    '*/Test/*',
  ]);
};

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

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
composer info --path --no-dev
composer info --path --no-dev
composer info --path --no-dev

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
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/資料夾以及包含所有生產依賴項的資料夾:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')"
$ vendor/bin/rector process src $paths
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')" $ vendor/bin/rector process src $paths
$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr '\n' ' ')"
$ vendor/bin/rector process src $paths

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

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
composer why-not php "7.1.*" | grep -o "\S*\/\S*"
composer why-not php "7.1.*" | grep -o "\S*\/\S*"
composer why-not php "7.1.*" | grep -o "\S*\/\S*"

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ composer install --no-dev
$ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*")
$ composer install
$ composer install --no-dev $ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*") $ composer install
$ composer install --no-dev
$ packages=$(composer why-not php "7.1.*" | grep -o "\S*\/\S*")
$ composer install

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# Executing this command
$ composer info psr/cache --path
# Produces this response:
psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache
# Executing this command $ composer info psr/cache --path # Produces this response: psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache
# Executing this command
$ composer info psr/cache --path   
# Produces this response:
psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache

我們對列表中的所有專案執行此命令,以獲取要轉換的所有路徑:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
for package in $packages
do
path=$(composer info $package --path | cut -d' ' -f2-)
paths="$paths $path"
done
for package in $packages do path=$(composer info $package --path | cut -d' ' -f2-) paths="$paths $path" done
for package in $packages
do
  path=$(composer info $package --path | cut -d' ' -f2-)
  paths="$paths $path"
done

最後,我們將此列表提供給Rector(加上專案的src/資料夾):

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
vendor/bin/rector process src $paths
vendor/bin/rector process src $paths
vendor/bin/rector process src $paths

轉換程式碼時需要避免的坑

轉換程式碼可以被視為一門藝術,通常需要針對專案進行調整。讓我們看看我們可能遇到的一些問題。

鏈式規則(Chained Rules)並不總是被處理的

鏈式規則是指規則需要轉換前一個規則生成的程式碼。

例如,庫symfony/cache包含以下程式碼

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
final class CacheItem implements ItemInterface
{
public function tag($tags): ItemInterface
{
// ...
return $this;
}
}
final class CacheItem implements ItemInterface { public function tag($tags): ItemInterface { // ... return $this; } }
final class CacheItem implements ItemInterface
{
  public function tag($tags): ItemInterface
  {
    // ...
    return $this;
  }
}

從PHP 7.4轉換到7.3時,函式標記必須經過兩次修改:

最終結果應該是:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
final class CacheItem implements ItemInterface
{
public function tag($tags)
{
// ...
return $this;
}
}
final class CacheItem implements ItemInterface { public function tag($tags) { // ... return $this; } }
final class CacheItem implements ItemInterface
{
  public function tag($tags)
  {
    // ...
    return $this;
  }
}

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
final class CacheItem implements ItemInterface
{
public function tag($tags): self
{
// ...
return $this;
}
}
final class CacheItem implements ItemInterface { public function tag($tags): self { // ... return $this; } }
final class CacheItem implements ItemInterface
{
  public function tag($tags): self
  {
    // ...
    return $this;
  }
}

問題是,Rector不能始終控制規則的應用順序

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

為了識別鏈式規則,我們在原始碼上執行了兩次Rector,如下所示:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$ vendor/bin/rector process src
$ vendor/bin/rector process src --dry-run
$ vendor/bin/rector process src $ vendor/bin/rector process src --dry-run
$ vendor/bin/rector process src
$ vendor/bin/rector process src --dry-run

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

使用–dry-run flag執行Rector

使用–dry-run flag執行Rector

一旦我們確定了未應用的鏈式規則,我們就可以建立另一個目錄配置檔案——例如,rector-chained-rule.php 將執行缺少的規則。這一次,我們不需要為 src/下的所有檔案處理一整套規則,而是可以在需要應用該規則的特定檔案上執行特定的缺失規則:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// 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',
]);
};
// 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', ]); };
// 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使用新的配置檔案:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
# 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
# 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
# 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依賴性可能不一致

庫可以宣告一個依賴項進行開發(即在composer.json中的require-dev 下),但仍然可以引用其中的一些程式碼進行生產(例如在 src/下的一些檔案上,而不是在 tests/ 下)。

通常,這不是問題,因為該程式碼可能不會載入到生產環境中,因此應用程式上永遠不會出現錯誤。但是,當Rector處理原始碼及其依賴項時,它會驗證是否可以載入所有引用的程式碼。如果任何檔案引用了未安裝庫中的某段程式碼(因為它被宣告為僅用於開發),Rector將丟擲一個錯誤。

例如,Symfony快取元件中的類 EarlyExpirationHandler 實現Messenger元件中的介面 MessageHandlerInterface :

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class EarlyExpirationHandler implements MessageHandlerInterface
{
//...
}
class EarlyExpirationHandler implements MessageHandlerInterface { //... }
class EarlyExpirationHandler implements MessageHandlerInterface
{
    //...
}

但是,symfony/cache宣告 symfony/messenger開發的依賴項。然後,在依賴symfony/cache的專案上執行Rector時,它將丟擲一個錯誤:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
[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".
[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".
[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".

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

  1. 在Rector配置中,跳過處理引用該程式碼段的檔案:
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    return static function (ContainerConfigurator $containerConfigurator): void {
    // ...
    $parameters->set(Option::SKIP, [
    __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php',
    ]);
    };
    return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php', ]); };
    return static function (ContainerConfigurator $containerConfigurator): void {
      // ...
    
      $parameters->set(Option::SKIP, [
        __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php',
      ]);
    };
  2. 下載缺少的庫並新增其路徑,以便由Rector自動載入:
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    return static function (ContainerConfigurator $containerConfigurator): void {
    // ...
    $parameters->set(Option::AUTOLOAD_PATHS, [
    __DIR__ . '/vendor/symfony/messenger',
    ]);
    };
    return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::AUTOLOAD_PATHS, [ __DIR__ . '/vendor/symfony/messenger', ]); };
    return static function (ContainerConfigurator $containerConfigurator): void {
      // ...
    
      $parameters->set(Option::AUTOLOAD_PATHS, [
        __DIR__ . '/vendor/symfony/messenger',
      ]);
    };
  3. 讓您的專案依賴於缺少的庫進行生產:
    Plain text
    Copy to clipboard
    Open code in new window
    EnlighterJS 3 Syntax Highlighter
    composer require symfony/messenger
    composer require symfony/messenger
    composer require symfony/messenger

轉換和連續整合

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

執行轉換過程的理想時間是為我們的專案生成釋出時。例如,下面的程式碼GitHub Actions的工作流,它建立了WordPress外掛的釋出:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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 }}
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 }}
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操作釋出WordPress外掛的標準過程。新新增的將外掛程式碼從PHP7.4轉換到7.1的步驟如下:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
- 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
- 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
      - 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

綜上所述,此工作流現在執行以下步驟:

  1. 從WordPress外掛的儲存庫中檢查WordPress外掛的原始碼,該外掛是用PHP7.4編寫的
  2. 安裝其Composer依賴項
  3. 將其程式碼從PHP7.4轉換到7.1
  4. 將外掛主檔案頭中的“Requires PHP”條目從“7.4”修改為“7.1”
  5. 刪除開發所需的依賴項
  6. 建立外掛的.zip檔案,排除所有不需要的檔案
  7. 將.zip檔案作為釋出資產上載(此外,還作為GitHub操作的工件上載)

測試轉換程式碼

一旦程式碼被轉換到 PHP 7.1,我們怎麼知道它工作得很好?或者,換句話說,我們怎麼知道它已經被徹底轉換,並且沒有留下更高版本的 PHP 程式碼的殘餘?

與轉存程式碼類似,我們可以在 CI 流程中實現解決方案。這個想法是設定與PHP 7.1的亞軍的環境,並在轉樁程式碼上執行一個襯墊。如果任何程式碼與 PHP 7.1 不相容(例如未轉換的 PHP 7.4 中的鍵入屬性),則襯裡會丟擲錯誤。

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

一旦程式碼被轉換到PHP7.1,我們如何知道它工作良好?或者,換句話說,我們如何知道它已經被徹底轉換,並且沒有留下更高版本的PHP程式碼的殘餘?

與轉換程式碼類似,我們可以在CI流程中實現解決方案。其想法是使用PHP7.1設定執行者環境,並在轉換的程式碼上執行linter。如果任何程式碼與PHP7.1不相容(例如PHP7.4中未轉換的型別化屬性),那麼linter將丟擲一個錯誤。

PHP的一個執行良好的linter是PHP並行Lint。我們可以將此庫作為開發依賴項安裝在我們的專案中,或者讓CI流程將其作為獨立的Composer專案安裝:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
composer create-project php-parallel-lint/php-parallel-lint
composer create-project php-parallel-lint/php-parallel-lint
composer create-project php-parallel-lint/php-parallel-lint

每當程式碼包含PHP 7.2及更高版本時,PHP Parallel Lint將丟擲如下錯誤:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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.
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.
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 工作流中。執行將程式碼從 PHP 8.0 轉換到 7.1 並測試它的步驟是:

  1. 檢視原始碼
  2. 讓環境執行 PHP 8.0,以便校長可以解釋原始碼
  3. 將程式碼轉換到 PHP 7.1
  4. 安裝 PHP 絨毛工具
  5. 將環境的 PHP 版本切換到 7.1
  6. 在轉存程式碼上執行襯墊

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

讓我們將linter新增到CI的工作流中。將程式碼從PHP 8.0轉換到7.1並進行測試的步驟如下:

  1. 檢視原始碼
  2. 讓環境執行PHP8.0,這樣校長就可以解釋原始碼了
  3. 將程式碼轉換到PHP7.1
  4. 安裝PHP linter工具
  5. 將環境的PHP版本切換到7.1
  6. 在轉換的程式碼上執行linter

此GitHub操作工作流執行以下任務:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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
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
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 檔案(不需要轉換)。這些檔案包含PHP8.0,因此linter在處理它們時會丟擲錯誤。但是,排除這些檔案是安全的,因為只有在執行PHP 8.0或更高版本時,才會在生產環境中載入這些檔案:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
if (\PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; }
if (\PHP_VERSION_ID >= 80000) {
  return require __DIR__.'/bootstrap80.php';
}

小結

本文教我們如何轉換PHP程式碼,允許我們在原始碼中使用PHP8.0,並建立一個適用於PHP7.1的發行版。Transpiling是通過Rector(一種PHP重構工具)完成的。

轉換程式碼使開發人員的功效效率更高,因為我們可以更好地捕獲開發中的錯誤,並生成自然更易於閱讀和理解的程式碼。

Transpiling還使我們能夠將具有特定PHP需求的程式碼與CMS分離。如果我們希望使用最新版本的PHP來建立公開可用的WordPress外掛或Drupal模組,而不嚴重限制我們的使用者群,那麼我們現在可以這樣做。

評論留言