Menulis aturan kustom Rector untuk downgrade
Gambaran umum
Bagian berjudul “Gambaran umum”Pipeline backport NextPDF melakukan downgrade pada kode sumber PHP 8.4 dari nextpdf/nextpdf agar dapat berjalan di PHP 8.1 dan, untuk inti, PHP 7.4. Pipeline ini menggunakan set downgrade bawaan Rector untuk sebagian besar fitur bahasa, ditambah sejumlah kecil aturan kustom untuk fitur yang belum dicakup Rector.
Panduan ini menunjukkan cara menambahkan aturan kustom baru. Aturan baru mengikuti struktur yang sama dengan tiga aturan yang sudah ada di repositori: DowngradeAsymmetricVisibilityRector, DowngradeCloneWithRector, dan DowngradeTraitConstantsRector. Ketiganya berada di rector/rules/, didaftarkan di rector/config/, dan diuji dengan fixture di tests/Rector/.
Gunakan panduan ini ketika fitur NextPDF memakai sintaks PHP yang tidak didukung target build dan Rector tidak menyediakan downgrade bawaan untuknya. Sebelum mulai, pastikan Rector memang belum memiliki aturan terkait. Rantai withDowngradeSets() bawaan sudah menangani kelas readonly, konstanta kelas bertipe, operator pipe, dan banyak fitur lainnya.
Pemasangan
Bagian berjudul “Pemasangan”Anda menulis dan menjalankan aturan kustom di dalam repositori nextpdf-backport. Berkas composer.json repositori tersebut mendeklarasikan alat pengembangan.
- Klon repositori backport dan pasang dependensinya.
- Pastikan Rector dan PHPUnit dapat di-resolve.
git clone https://github.com/nextpdf-labs/backport.gitcd backportcomposer installvendor/bin/rector --versionvendor/bin/phpunit --versionRepositori ini memerlukan PHP 8.4 untuk berjalan karena aturannya memanipulasi pohon sintaks 8.4, meskipun keluaran build menargetkan PHP 8.1 atau 7.4. composer.jsonrequire mengunci php pada >=8.4 <9.0. Aturan kustom dimuat otomatis di bawah namespace NextPDF\Backport\, yang dipetakan ke rector/rules/; pengujian dimuat otomatis di bawah NextPDF\Backport\Tests\, yang dipetakan ke tests/.
Anatomi aturan
Bagian berjudul “Anatomi aturan”Setiap aturan kustom memperluas Rector\Rector\AbstractRector dan mengimplementasikan tiga metode. Kontraknya sama untuk ketiga aturan yang sudah ada.
| Anggota | Tipe | Tujuan |
|---|---|---|
getRuleDefinition() | RuleDefinition | Deskripsi yang dapat dibaca manusia serta pasangan before/after CodeSample untuk generator dokumentasi aturan. |
getNodeTypes() | array<class-string<Node>> | Kelas node Abstract Syntax Tree (AST) yang dikunjungi aturan. Rector memanggil refactor() hanya untuk kelas-kelas ini. |
refactor(Node $node) | ?Node atau `Stmt[] | null` |
Deklarasi tipe node mengarahkan aturan. DowngradeAsymmetricVisibilityRector menargetkan [Property::class, Param::class] karena asymmetric visibility (public private(set)) dapat muncul pada properti kelas maupun parameter konstruktor yang dipromosikan. DowngradeTraitConstantsRector menargetkan [Trait_::class] karena aturan ini menulis ulang seluruh isi trait dalam satu lintasan. DowngradeCloneWithRector menargetkan [FileNode::class, Return_::class, Expression::class] karena clone-with (clone($obj, [...])) dapat muncul di posisi return maupun penugasan; aturan ini memakai kunjungan ke FileNode untuk mereset penghitung per berkas.
Aturan yang mengembalikan null dari refactor() menandakan bahwa tidak ada perubahan. Aturan yang mengembalikan sebuah node menandakan penggantian. Aturan yang mengembalikan Stmt[], yaitu daftar pernyataan, memperluas satu pernyataan menjadi beberapa pernyataan. Inilah cara DowngradeCloneWithRector mengubah satu return clone($this, [...]); menjadi penugasan clone, satu penugasan properti per override, dan satu return terakhir.
Yang sudah ditangani set bawaan
Bagian berjudul “Yang sudah ditangani set bawaan”Kedua konfigurasi pipeline memanggil ->withDowngradeSets(php81: true) dan ->withDowngradeSets(php74: true). Set tersebut merangkai semua aturan downgrade bawaan untuk target masing-masing. Aturan kustom hanya mengisi celah: asymmetric visibility PHP 8.4, clone-with PHP 8.5, dan konstanta trait PHP 8.2. Rector tidak melakukan downgrade untuk fitur-fitur ini secara mandiri. Tulis aturan kustom hanya setelah Anda memastikan ada celah yang sama.
Langkah demi langkah: menulis aturan
Bagian berjudul “Langkah demi langkah: menulis aturan”Prosedur ini menambahkan aturan baru. Contoh yang digunakan mencerminkan aturan yang sudah ada; ganti dengan fitur Anda sendiri.
- Buat kelas aturan di
rector/rules/. Beri namaDowngrade<Feature>Rectordan tempatkan di namespaceNextPDF\Backportagar pemetaan autoload yang sudah ada menemukannya. - Perluas
Rector\Rector\AbstractRectordan tandai kelas sebagaifinal. - Implementasikan
getNodeTypes()untuk mengembalikan kumpulan kelas node AST tersempit yang dibutuhkan aturan. Kumpulan yang lebih sempit membuat Rector mengunjungi lebih sedikit node. - Implementasikan
refactor(). Pastikan node tersebut termasuk salah satu tipe yang dideklarasikan, pasang guard untuk sintaks persis yang Anda targetkan, lakukan transformasi, lalu kembalikan node baru,Stmt[], ataunullbila tidak ada perubahan. - Implementasikan
getRuleDefinition()dengan satu before/afterCodeSampleuntuk setiap kasus berbeda yang ditangani aturan. - Jaga berkas tetap sesuai PHPStan Level 10: beri tipe pada setiap parameter, nilai kembalian, dan properti, serta gunakan generic PHPDoc untuk mendeskripsikan bentuk larik.
Aturan asymmetric-visibility adalah contoh lengkap paling kecil. Aturan ini menghapus flag set-visibility dan memastikan visibilitas baca dasar tetap ada:
<?php
declare(strict_types=1);
namespace NextPDF\Backport;
use PhpParser\Modifiers;use PhpParser\Node;use PhpParser\Node\Param;use PhpParser\Node\Stmt\Property;use Rector\Rector\AbstractRector;use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
final class DowngradeAsymmetricVisibilityRector extends AbstractRector{ public function getRuleDefinition(): RuleDefinition { return new RuleDefinition( 'Remove asymmetric visibility modifiers (public private(set) -> public)', [ new CodeSample( 'public private(set) float $x = 0.0;', 'public float $x = 0.0;', ), ], ); }
/** * @return array<class-string<Node>> */ public function getNodeTypes(): array { return [Property::class, Param::class]; }
public function refactor(Node $node): ?Node { \assert($node instanceof Property || $node instanceof Param);
if (($node->flags & Modifiers::VISIBILITY_SET_MASK) === 0) { return null; }
$node->flags &= ~Modifiers::VISIBILITY_SET_MASK;
if (($node->flags & Modifiers::VISIBILITY_MASK) === 0) { $node->flags |= Modifiers::PUBLIC; }
return $node; }}Guard pada if pertama sangat penting. Ketika properti tidak memiliki flag set-visibility, aturan mengembalikan null dan membiarkan node tidak berubah. Aturan yang melakukan transformasi tanpa syarat akan menulis ulang kode yang seharusnya dibiarkan apa adanya.
Fixture dan pengujian
Bagian berjudul “Fixture dan pengujian”Setiap aturan memiliki pengujian berbasis fixture yang menjalankan aturan terhadap berkas .php.inc dan memastikan keluarannya cocok. Harness pengujiannya berasal dari Rector\Testing\PHPUnit\AbstractRectorTestCase milik Rector.
Test case berukuran kecil dan konsisten di antara ketiga aturan yang sudah ada:
<?php
declare(strict_types=1);
namespace NextPDF\Backport\Tests\Rector;
use Iterator;use PHPUnit\Framework\Attributes\DataProvider;use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class DowngradeTraitConstantsRectorTest extends AbstractRectorTestCase{ #[DataProvider('provideData')] public function test(string $filePath): void { $this->doTestFile($filePath); }
public static function provideData(): Iterator { return self::yieldFilesFromDirectory(__DIR__ . '/Fixtures/DowngradeTraitConstants'); }
public function provideConfigFilePath(): string { return __DIR__ . '/config/downgrade_trait_constants.php'; }}Pengujian ini menunjuk ke berkas konfigurasi per aturan di tests/Rector/config/ yang hanya mendaftarkan aturan yang sedang diuji, sehingga setiap fixture menguji satu aturan secara terisolasi:
<?php
declare(strict_types=1);
use NextPDF\Backport\DowngradeTraitConstantsRector;use Rector\Config\RectorConfig;
return RectorConfig::configure() ->withRules([ DowngradeTraitConstantsRector::class, ]);Fixture adalah berkas .php.inc yang berisi masukan, pemisah -----, dan keluaran yang diharapkan. Ketika aturan tidak membuat perubahan, hilangkan pemisah dan blok kedua. Fixture yang mengalami transformasi untuk aturan trait-constants terlihat seperti ini:
<?php
trait HasLimit{ private const MAX_SIZE = 1024;
public function getLimit(): int { return self::MAX_SIZE; }}?>-----<?php
trait HasLimit{ private static $MAX_SIZE = 1024;
public function getLimit(): int { return self::$MAX_SIZE; }}?>Untuk menulis pengujian aturan baru:
- Buat
tests/Rector/Fixtures/Downgrade<Feature>/dan tambahkan satu.php.incper kasus. - Cakup kasus yang mengalami transformasi dengan pemisah
-----dan kasus yang dilewati tanpa pemisah, seperti properti tanpa set visibility yang harus diteruskan tanpa perubahan. - Tambahkan
tests/Rector/config/downgrade_<feature>.phpyang mendaftarkan hanya aturan Anda. - Tambahkan
tests/Rector/Downgrade<Feature>RectorTest.phpyang menghasilkan direktori fixture dan menunjuk ke konfigurasi. - Jalankan suite pengujiannya.
composer testRepositori ini juga menyertakan RectorRulesBehaviorTest dan RectorRulesMetadataTest, yang menegaskan perilaku lintas aturan dan memastikan bahwa getRuleDefinition() setiap aturan terbentuk dengan baik. Jalankan composer test secara penuh agar semua gate tersebut melihat aturan baru Anda.
Mengintegrasikan ke dalam build
Bagian berjudul “Mengintegrasikan ke dalam build”Aturan tidak aktif dalam build sampai Anda mendaftarkannya di konfigurasi pipeline. Ada dua target build, masing-masing dengan konfigurasinya sendiri di rector/config/.
- Buka
rector/config/rector-php81.phpdan tambahkan kelas aturan Anda ke daftar->withRules([...]). - Jika fitur tersebut juga harus diturunkan versinya untuk build inti PHP 7.4, tambahkan kelas yang sama ke
rector/config/rector-php74.php. - Tambahkan komentar yang menyebutkan versi PHP yang memperkenalkan fitur tersebut, sesuai entri yang sudah ada.
<?php
declare(strict_types=1);
use NextPDF\Backport\DowngradeAsymmetricVisibilityRector;use NextPDF\Backport\DowngradeCloneWithRector;use NextPDF\Backport\DowngradeTraitConstantsRector;use Rector\Config\RectorConfig;use Rector\DowngradePhp81\Rector\Property\DowngradeReadonlyPropertyRector;
return RectorConfig::configure() ->withDowngradeSets(php81: true) ->withRules([ // PHP 8.4 — asymmetric visibility (not covered by built-in sets) DowngradeAsymmetricVisibilityRector::class,
// PHP 8.4 — clone-with syntax (not covered by built-in sets) DowngradeCloneWithRector::class,
// PHP 8.2 — trait constants (not covered by built-in sets) DowngradeTraitConstantsRector::class,
// PHP 8.1 — readonly property removal (for clone-with expansion safety) DowngradeReadonlyPropertyRector::class, ]);Orkestrator build (scripts/build.php) menggabungkan repositori sumber, menjalankan Rector dengan konfigurasi ini, menyesuaikan composer.json yang dihasilkan, dan menjalankan pemeriksaan sintaks php -l terhadap keluarannya. Verifikasi aturan Anda dengan PHPStan dan build penuh sebelum mengandalkannya.
composer analysecomposer build:dryKasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”- Urutan pendaftaran tidak penting, tetapi urutan aturan penting secara konseptual. Mekanisme multi-lintasan Rector menelusuri ulang sampai tidak ada lagi aturan yang membuat perubahan, sehingga Anda tidak perlu mengurutkan aturan secara manual di konfigurasi. Meski begitu, dokumentasikan setiap ketergantungan urutan di docblock kelas, seperti yang dilakukan
DowngradeCloneWithRector: ekspansinya menghasilkan$clone->prop = $val, yang akan gagal pada properti readonly, sehinggaDowngradeReadonlyPropertyRectorharus dijalankan untuk target yang sama. - Berikan atribut kosong saat membangun node pengganti dari jenis node yang berbeda.
DowngradeTraitConstantsRectormembangunPropertydariClassConstdan memberikan[]sebagai atribut alih-alih atribut node sumber. Jika Anda membawa atribut asli, pointerorigNodetetap mengarah ke jenis node yang salah dan memicu asersi pada printer yang menjaga format. - Reset status per berkas pada kunjungan
FileNode.DowngradeCloneWithRectormendeklarasikanFileNode::classdigetNodeTypes()hanya untuk mereset penghitung variabel sementaranya di awal setiap berkas, sehingga nama variabel yang dihasilkan tidak berbenturan di antara berkas. - Pasang guard dengan tepat, lalu kembalikan
null. Transformasi clone-with harus memastikan bahwa nama panggilannya adalahclonedan bahwa argumen kedua adalah literal larik sebelum bertindak;clone $objbiasa tidak pernah mencapai aturan sebagai panggilan fungsi, dan panggilan dua-argumen yang argumen keduanya bukan larik dibiarkan apa adanya. - Buang modifier yang tidak dapat diekspresikan target. Ketika aturan trait-constants mengubah konstanta menjadi properti statis, aturan tersebut mempertahankan visibilitas dan menambahkan
static, tetapi tidak boleh membawa modifierfinalkarena properti PHP 8.1 tidak bisa bersifat final. - Jaga aturan tetap di PHPStan Level 10. Repositori menjalankan
composer analysepada level 10 atasrector/rulesdanscripts. Beri tipe pada setiap signature dan anotasikan bentuk larik; aturan yang tidak lolos analisis adalah cacat, bukan draf.
Lihat juga
Bagian berjudul “Lihat juga”- Panduan pengembang Backport Builder — arsitektur pipeline, model branch, dan artefak rilis yang terkait dengan aturan-aturan ini.
- Referensi API Backport — API surface yang dipublikasikan untuk perkakas build backport.
- Konfigurasi Backport — target build dan pemilihan downgrade-set.
- Pemecahan masalah Backport — cara mendiagnosis kegagalan dalam pohon downgrade yang dihasilkan.
- Dokumentasi Rector — referensi hulu untuk
AbstractRector,RuleDefinition, dan kelas node AST yang digunakan digetNodeTypes().