İçeriğe geç

Özel Rector geri uyarlama kuralı yazma

NextPDF backport ardışık düzeni, nextpdf/nextpdf projesinin PHP 8.4 kaynağını PHP 8.1’de ve çekirdek için PHP 7.4’te çalışabilecek şekilde geri uyarlar. Dil özelliklerinin çoğu için Rector’ün yerleşik geri uyarlama setlerini, Rector’ün kapsamadığı özellikler içinse küçük bir özel kural kümesini kullanır.

Bu kılavuz, yeni bir özel kuralın nasıl ekleneceğini gösterir. Depoda zaten bulunan üç kuralla aynı yapıyı izler: DowngradeAsymmetricVisibilityRector, DowngradeCloneWithRector ve DowngradeTraitConstantsRector. Üçü de rector/rules/ içinde bulunur, rector/config/ içinde kayıtlıdır ve tests/Rector/ içinde fikstür tabanlı testlere sahiptir.

Bu kılavuzu, bir NextPDF özelliği derleme hedefinin desteklemediği bir PHP söz dizimi kullandığında ve Rector’ün bunun için yerleşik bir geri uyarlaması bulunmadığında kullanın. Başlamadan önce, Rector’de bunun için gerçekten bir kural bulunmadığını doğrulayın. Yerleşik withDowngradeSets() zinciri, salt okunur sınıfları, tür bildirimli sınıf sabitlerini, boru operatörünü ve diğer pek çok özelliği zaten ele alır.

Özel kuralları nextpdf-backport deposu içinde yazıp çalıştırırsınız. Geliştirme araçları bu deponun composer.json dosyasında bildirilir.

  1. Backport deposunu klonlayın ve bağımlılıklarını yükleyin.
  2. Rector ile PHPUnit’in çözümlendiğini doğrulayın.
Terminal window
git clone https://github.com/nextpdf-labs/backport.git
cd backport
composer install
Terminal window
vendor/bin/rector --version
vendor/bin/phpunit --version

Derleme çıktısı PHP 8.1 veya 7.4 hedeflese de, kurallar 8.4 söz dizimi ağaçlarını işlediği için deponun çalışması için PHP 8.4 gerekir. composer.jsonrequire, php sürümünü >=8.4 <9.0 olarak sabitler. Özel kurallar NextPDF\Backport\ ad alanı altında otomatik yüklenir ve bu ad alanı rector/rules/ dizinine eşlenir; testler ise NextPDF\Backport\Tests\ altında otomatik yüklenir ve tests/ dizinine eşlenir.

Her özel kural Rector\Rector\AbstractRector sınıfını genişletir ve üç yöntem uygular. Sözleşme, mevcut üç kuralın tamamı için aynıdır.

ÜyeTürAmaç
getRuleDefinition()RuleDefinitionKural belgesi oluşturucu için, insan tarafından okunabilir bir açıklama ve before/after CodeSample çiftleri.
getNodeTypes()array<class-string<Node>>Kuralın ziyaret ettiği soyut söz dizimi ağacı (AST) düğüm sınıfları. Rector, refactor() yöntemini yalnızca bu sınıflar için çağırır.
refactor(Node $node)?Node veya `Stmt[]null`

Düğüm türü bildirimi, kuralın nerede çalışacağını belirler. DowngradeAsymmetricVisibilityRector, [Property::class, Param::class] hedefler; çünkü asimetrik görünürlük (public private(set)) hem bir sınıf özelliğinde hem de yükseltilmiş bir kurucu parametresinde görünebilir. DowngradeTraitConstantsRector, tüm trait gövdesini tek geçişte yeniden yazdığı için [Trait_::class] hedefler. DowngradeCloneWithRector, [FileNode::class, Return_::class, Expression::class] hedefler; çünkü clone-with (clone($obj, [...])) hem return hem de atama konumlarında görünebilir; dosya başına bir sayacı sıfırlamak için FileNode ziyaretini kullanır.

Bir kuralın null değerini refactor() yönteminden döndürmesi “değişiklik yok” anlamına gelir. Düğüm döndürmesi bir değiştirmeyi belirtir. Stmt[] deyim listesi döndürmesi ise bir deyimi birkaç deyime genişletir. DowngradeCloneWithRector, tek bir return clone($this, [...]); ifadesini bir clone atamasına, geçersiz kılma başına bir özellik atamasına ve son bir return ifadesine bu şekilde dönüştürür.

İki ardışık düzen yapılandırması ->withDowngradeSets(php81: true) ve ->withDowngradeSets(php74: true) çağrılarını kullanır. Bu setler, hedef için geçerli tüm yerleşik geri uyarlama kurallarını zincirler. Özel kurallar yalnızca boşluklar için vardır: PHP 8.4 asimetrik görünürlük, PHP 8.5 clone-with ve PHP 8.2 trait sabitleri. Rector bunların hiçbirini kendi başına geri uyarlamaz. Özel kuralı yalnızca aynı boşluğu doğruladıktan sonra yazın.

Bu süreç yeni bir kural ekler. Çalışan örnek, mevcut kuralları temel alır; kendi özelliğinizi bunun yerine koyun.

  1. Kural sınıfını rector/rules/ içinde oluşturun. Adını Downgrade<Feature>Rector olarak belirleyin ve mevcut otomatik yükleme eşlemesinin onu seçebilmesi için NextPDF\Backport ad alanına yerleştirin.
  2. Kural sınıfını Rector\Rector\AbstractRector ile genişletin ve sınıfı final olarak işaretleyin.
  3. Kuralın ihtiyaç duyduğu en dar AST düğüm sınıfı kümesini döndürmek için getNodeTypes() yöntemini uygulayın. Daha dar bir küme, Rector’ün daha az düğüm ziyaret etmesini sağlar.
  4. Kuralın refactor() yöntemini uygulayın. Düğümün bildirilen türlerden biri olduğunu doğrulayın, yalnızca hedeflediğiniz tam söz dizimiyle eşleşen durumları dönüştürün ve yeni düğümü, Stmt[] değerini veya değişiklik olmadığında null döndürün.
  5. Kuralın getRuleDefinition() yöntemini, ele aldığı her ayrı durum için bir before/after CodeSample içerecek şekilde uygulayın.
  6. Dosyayı PHPStan düzey 10’da tutun: her parametre, dönüş ve özellik için tür belirtin ve dizi şekillerini tanımlamak için PHPDoc jeneriklerini kullanın.

Asimetrik görünürlük kuralı, eksiksiz en küçük örnektir. Set görünürlüğü bayraklarını kaldırır ve temel bir okuma görünürlüğünün kaldığından emin olur:

rector/rules/DowngradeAsymmetricVisibilityRector.php
<?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;
}
}

İlk if koşulundaki koruma kritik önemdedir. Özellikte set görünürlüğü bayrağı yoksa kural null döndürür ve düğümü değiştirmeden bırakır. Koşulsuz dönüştüren bir kural, kendi haline bırakması gereken kodu yeniden yazardı.

Her kuralın, kuralı .php.inc dosyalarına karşı çalıştıran ve çıktının eşleştiğini doğrulayan fikstür tabanlı bir testi vardır. Test altyapısı, Rector’ün Rector\Testing\PHPUnit\AbstractRectorTestCase sınıfına dayanır.

Test durumu küçüktür ve mevcut üç kural genelinde tutarlıdır:

tests/Rector/DowngradeTraitConstantsRectorTest.php
<?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';
}
}

Test, tests/Rector/config/ içinde yalnızca test edilen kuralı kaydeden, kurala özgü bir yapılandırma dosyasına işaret eder; böylece her fikstür kuralı yalıtılmış şekilde çalıştırır:

tests/Rector/config/downgrade_trait_constants.php
<?php
declare(strict_types=1);
use NextPDF\Backport\DowngradeTraitConstantsRector;
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withRules([
DowngradeTraitConstantsRector::class,
]);

Bir fikstür, .php.inc biçiminde bir dosyadır; girdiyi, bir ----- ayırıcısını ve beklenen çıktıyı içerir. Kural hiçbir değişiklik yapmadığında ayırıcıyı ve ikinci bloğu atlayın. Trait sabitleri kuralı için dönüşüm yapan bir fikstür şöyle görünür:

tests/Rector/Fixtures/DowngradeTraitConstants/private_constant.php.inc
<?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;
}
}
?>

Yeni bir kuralın testlerini yazmak için:

  1. Yeni bir tests/Rector/Fixtures/Downgrade<Feature>/ dizini oluşturun ve her durum için bir .php.inc ekleyin.
  2. Dönüştüren durumları bir ----- ayırıcısıyla, atlama durumlarını ise ayırıcısız kapsayın; örneğin değiştirilmeden geçmesi gereken, set görünürlüğü olmayan bir özelliği ekleyin.
  3. Yalnızca kuralınızı kaydeden bir tests/Rector/config/downgrade_<feature>.php dosyası ekleyin.
  4. Fikstür dizinini sağlayan ve yapılandırmaya işaret eden bir tests/Rector/Downgrade<Feature>RectorTest.php dosyası ekleyin.
  5. Test paketini çalıştırın.
Terminal window
composer test

Depo ayrıca RectorRulesBehaviorTest ve RectorRulesMetadataTest testlerini de içerir; bunlar çapraz kural davranışını doğrular ve her kuralın getRuleDefinition() yönteminin iyi biçimlendirilmiş olduğunu teyit eder. Yeni kuralınızın bu denetimlerden geçmesi için composer test komutunun tamamını çalıştırın.

Bir kural, ardışık düzen yapılandırmalarına kaydedilene kadar derlemede etkin değildir. İki derleme hedefinin her birinin rector/config/ içinde kendi yapılandırması vardır.

  1. Önce rector/config/rector-php81.php dosyasını açın ve kural sınıfınızı ->withRules([...]) listesine ekleyin.
  2. Özelliğin PHP 7.4 çekirdek derlemesi için de geri uyarlanması gerekiyorsa, aynı sınıfı rector/config/rector-php74.php dosyasına ekleyin.
  3. Mevcut girişlerle tutarlı olacak şekilde, özelliği tanıtan PHP sürümünü adlandıran bir yorum ekleyin.
rector/config/rector-php81.php
<?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,
]);

Derleme düzenleyicisi (scripts/build.php) kaynak depolarını birleştirir, Rector’ü bu yapılandırmalarla çalıştırır, oluşturulan composer.json dosyasını ayarlar ve çıktı üzerinde bir php -l söz dizimi denetimi çalıştırır. Kuralınıza güvenmeden önce onu PHPStan ve tam derleme ile doğrulayın.

Terminal window
composer analyse
composer build:dry
  • Kayıt sırası önemli değildir, ancak kural sırası kavramsal olarak önemlidir. Rector’ün çok geçişli mekanizması, hiçbir kural başka bir değişiklik yapmayana kadar yeniden çalışır; bu nedenle kuralları yapılandırmada elle sıralamanız gerekmez. Yine de, DowngradeCloneWithRector kuralının yaptığı gibi, herhangi bir sıralama bağımlılığını sınıf belge bloğunda belgeleyin: bu kuralın genişletme işlemi, salt okunur bir özellikte başarısız olacak $clone->prop = $val üretir; bu nedenle DowngradeReadonlyPropertyRector aynı hedef için çalışmalıdır.
  • Farklı türden bir düğümden yedek düğüm oluştururken boş öznitelikler geçirin. DowngradeTraitConstantsRector, bir Property öğesini bir ClassConst öğesinden oluşturur ve öznitelikler için kaynak düğümün öznitelikleri yerine [] geçirir. Özgün öznitelikleri taşırsanız, yanlış düğüm türüne bir origNode işaretçisi bırakırsınız ve biçim koruyan yazıcıda bir doğrulamayı tetiklersiniz.
  • Dosya başına durumu FileNode ziyaretinde sıfırlayın. DowngradeCloneWithRector, yalnızca her dosyanın başlangıcında geçici değişken sayacını sıfırlamak için FileNode::class öğesini getNodeTypes() içinde bildirir; böylece üretilen değişken adları dosyalar arasında çakışmaz.
  • Tam eşleşmeyi denetleyin, sonra null döndürün. Bir clone-with dönüşümü, eyleme geçmeden önce çağrı adının clone olduğunu ve ikinci bağımsız değişkenin bir dizi sabit değeri olduğunu doğrulamalıdır; düz bir clone $obj kurala asla bir işlev çağrısı olarak ulaşmaz ve ikinci bağımsız değişkeni bir dizi olmayan iki bağımsız değişkenli bir çağrı kendi haline bırakılır.
  • Hedefin ifade edemediği değiştiricileri ayıklayın. Trait sabitleri kuralı bir sabiti statik bir özelliğe dönüştürdüğünde, görünürlüğü korur ve static ekler, ancak PHP 8.1’de özellikler final olamayacağı için bir final değiştiricisi taşımamalıdır.
  • Kuralı PHPStan düzey 10’da tutun. Depo, composer analyse komutunu rector/rules ve scripts üzerinde düzey 10’da çalıştırır. Her imza için tür belirtin ve dizi şekillerini açıklamalarla tanımlayın; çözümleyiciden geçemeyecek bir kural taslak değil, kusurdur.
  • Backport Builder geliştirici kılavuzu — bu kuralların çevresindeki ardışık düzen mimarisi, dal modeli ve sürüm yapıtları.
  • Backport API başvurusu — backport derleme araçları için yayımlanmış API yüzeyi.
  • Backport yapılandırması — derleme hedefleri ve geri uyarlama seti seçimi.
  • Backport sorun giderme — oluşturulan bir geri uyarlama ağacındaki başarısızlıkların nasıl tanılanacağı.
  • Rector belgeleri — AbstractRector, RuleDefinition ve getNodeTypes() içinde kullanılan AST düğüm sınıfları için üst akış başvurusu.