Rector 降版規則¶
nextpdf/backport 包含三條針對 NextPDF Core 的自訂 Rector 規則,處理標準 Rector 規則集無法覆蓋的 PHP 8.5 特性降版場景。這些規則在 AST(抽象語法樹)層級進行轉換,確保語義正確性。
規則清單¶
| 規則類別 | 處理的 PHP 特性 | 目標版本 |
|---|---|---|
AsymmetricVisibilityRector | public private(set) / public protected(set) 非對稱存取修飾 | PHP 8.1, PHP 7.4 |
ReadonlyClassRector | readonly class(PHP 8.2)→ 所有屬性加 readonly | PHP 8.1, PHP 7.4 |
EnumToClassRector | enum(PHP 8.1)→ abstract class + 常數模擬 | PHP 7.4 only |
規則 1:AsymmetricVisibilityRector¶
目的¶
PHP 8.4 引入非對稱屬性存取修飾(Asymmetric Property Visibility),允許對讀寫操作指定不同的存取層級:
// PHP 8.4+ 語法(Core 中使用)
final class RenderingContext
{
public int $currentPage { get; private set; } // 公開讀、私有寫
public string $fontFamily { get; protected set; } // 公開讀、保護寫
}
PHP 8.1 和 7.4 不支援此語法,規則將其轉換為傳統的 private 屬性 + public getter:
轉換輸出(PHP 8.1 / 7.4)¶
final class RenderingContext
{
private int $currentPage = 0;
private string $fontFamily = '';
/** @return int */
public function getCurrentPage(): int
{
return $this->currentPage;
}
/** @return string */
public function getFontFamily(): string
{
return $this->fontFamily;
}
}
規則設定¶
// rector.php(PHP81 分支)
use NextPDF\Backport\Rector\AsymmetricVisibilityRector;
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withRules([
AsymmetricVisibilityRector::class,
])
->withPaths([__DIR__ . '/../../nextpdf/src']);
規則 2:ReadonlyClassRector¶
目的¶
PHP 8.2 引入 readonly class,宣告類別中所有屬性均為 readonly。PHP 8.1 支援 readonly 屬性但不支援 readonly class,PHP 7.4 不支援 readonly 屬性。
// PHP 8.2+ 語法(Core 中使用)
readonly class PageSize
{
public function __construct(
public float $width,
public float $height,
public string $unit = 'mm',
) {}
}
轉換輸出(PHP 8.1)¶
final class PageSize
{
public function __construct(
public readonly float $width,
public readonly float $height,
public readonly string $unit = 'mm',
) {}
}
轉換輸出(PHP 7.4)¶
final class PageSize
{
/** @var float */
private float $width;
/** @var float */
private float $height;
/** @var string */
private string $unit;
public function __construct(
float $width,
float $height,
string $unit = 'mm'
) {
$this->width = $width;
$this->height = $height;
$this->unit = $unit;
}
public function getWidth(): float { return $this->width; }
public function getHeight(): float { return $this->height; }
public function getUnit(): string { return $this->unit; }
}
規則 3:EnumToClassRector¶
目的¶
PHP 8.1 Enum 是 NextPDF Core 廣泛使用的特性,PHP 7.4 不支援 enum 關鍵字。EnumToClassRector 將 Backed Enum 轉換為 abstract class + class constant + 靜態工廠方法的模擬模式:
// PHP 8.1+ 語法(Core 中使用)
enum Conformance: string
{
case PDF_A4 = 'PDF/A-4';
case PDF_UA2 = 'PDF/UA-2';
case PDF_A4UA2 = 'PDF/A-4u';
public function label(): string
{
return match($this) {
self::PDF_A4 => 'PDF/A-4 Archival',
self::PDF_UA2 => 'PDF/UA-2 Accessible',
self::PDF_A4UA2 => 'PDF/A-4u Archival + Accessible',
};
}
}
轉換輸出(PHP 7.4)¶
/** @psalm-immutable */
abstract class Conformance
{
public const PDF_A4 = 'PDF/A-4';
public const PDF_UA2 = 'PDF/UA-2';
public const PDF_A4UA2 = 'PDF/A-4u';
/** @var string */
private string $value;
final private function __construct(string $value)
{
$this->value = $value;
}
public static function PDF_A4(): self { return new static(self::PDF_A4); }
public static function PDF_UA2(): self { return new static(self::PDF_UA2); }
public static function PDF_A4UA2(): self { return new static(self::PDF_A4UA2); }
public function value(): string { return $this->value; }
public static function from(string $value): self
{
foreach ([self::PDF_A4, self::PDF_UA2, self::PDF_A4UA2] as $case) {
if ($case === $value) { return new static($value); }
}
throw new \ValueError("\"{$value}\" is not a valid backing value for enum " . static::class);
}
public static function tryFrom(string $value): ?self
{
try { return static::from($value); }
catch (\ValueError $e) { return null; }
}
public function label(): string
{
switch ($this->value) {
case self::PDF_A4: return 'PDF/A-4 Archival';
case self::PDF_UA2: return 'PDF/UA-2 Accessible';
case self::PDF_A4UA2: return 'PDF/A-4u Archival + Accessible';
}
}
}
執行規則¶
# 在 nextpdf-backport 倉庫根目錄執行(PHP74 分支)
php vendor/bin/rector process --config=rector.php --dry-run # 預覽轉換
php vendor/bin/rector process --config=rector.php # 執行轉換
# 驗證轉換結果
composer phpstan # PHPStan Level 9
composer test # PHPUnit 測試套件
composer cs-check # PSR-12 檢查
規則測試¶
每條 Rector 規則均有獨立的測試用例,以輸入 PHP 檔案 + 預期輸出 PHP 檔案的形式存放:
tests/
├── Rector/
│ ├── AsymmetricVisibilityRectorTest.php
│ │ ├── Fixture/
│ │ │ ├── asymmetric_visibility.php.inc # 輸入
│ │ │ └── asymmetric_visibility_expected.php # 預期輸出
│ ├── ReadonlyClassRectorTest.php
│ │ └── Fixture/
│ └── EnumToClassRectorTest.php
│ └── Fixture/
參見¶
- Backport 套件總覽 — 套件架構與降版流程
- PHP 版本支援 — 雙分支模型、PHP 版本矩陣