跳轉到

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/
# 只執行 Rector 規則測試
composer test -- --filter=Rector

參見