Configuring the NextPDF Backport Builder
Build tooling — NOT a runtime dependency. Everything on this page configures source transformation at build time. None of it ships to a downstream runtime.
At a glance
Section titled “At a glance”Three Rector configuration files under rector/config/ and three custom rules under rector/rules/ drive the transformation. The selected configuration depends on the target. The PHP 8.1 target uses one Rector pass. The PHP 7.4 target uses a two-pass pipeline with a fix-up stage between passes. The second pass is required because Rector cannot resolve enum case defaults in one traversal.
Rector configurations
Section titled “Rector configurations”| File | Purpose | Used by |
|---|---|---|
rector/config/rector-php81.php | Single-pass downgrade to PHP 8.1 | PHP 8.1 target |
rector/config/rector-php74-enums.php | Pass 1 — convert enums to constant-list classes | PHP 7.4 target |
rector/config/rector-php74.php | Pass 2 — full downgrade to PHP 7.4 | PHP 7.4 target |
Each configuration calls Rector’s downgrade-set chain (withDowngradeSets(php81: true) or withDowngradeSets(php74: true)) for features Rector handles natively. It then registers custom rules for the remaining features. Verified against the three files in rector/config/.
Skip rules
Section titled “Skip rules”rector-php81.php and rector-php74.php both skip */tests/Benchmark/*. The benchmark scripts reference external Portable Document Format (PDF) libraries that Rector cannot resolve, which crashes its default-parameter resolver. rector-php74.php also skips DowngradeHashAlgorithmXxHashRector. That built-in rule crashes on the MHASH_XXH* constants deprecated in modern PHP, and the source does not use xxHash. Verified against the withSkip() calls in both files.
The three custom rules
Section titled “The three custom rules”The repository ships exactly three custom Rector rules. All three are registered in rector-php81.php; the asymmetric-visibility, clone-with, and trait-constants rules are also registered in rector-php74.php. tests/Rector/RectorRulesMetadataTest.php asserts this directly by constructing each rule and checking its definition and node types.
DowngradeAsymmetricVisibilityRector
Section titled “DowngradeAsymmetricVisibilityRector”Removes the asymmetric-visibility set modifier. A property or promoted parameter declared public private(set) becomes plain public. Read access is preserved; the compile-time setter restriction is dropped. When no read visibility remains, the rule defaults to public. Source-backed transformation from tests/Rector/Fixtures/DowngradeAsymmetricVisibility/public_private_set.php.inc:
<?php
class Config { public private(set) float $x = 0.0; public private(set) string $name = ''; public private(set) int $count = 0;}<?php
class Config { public float $x = 0.0; public string $name = ''; public int $count = 0;}DowngradeCloneWithRector
Section titled “DowngradeCloneWithRector”Rewrites the clone() function form with an overrides array into a clone, explicit property assignments, and a return of a temporary variable. The temporary-variable counter resets per file. This rule must run after the readonly-property removal rule, because the expanded assignment would otherwise fail against a readonly property. Source-backed transformation from tests/Rector/Fixtures/DowngradeCloneWith/return_clone_with.php.inc:
<?php
class PageSize { public float $width = 0.0; public float $height = 0.0;
public function withDimensions(float $width, float $height): self { return clone($this, ['width' => $width, 'height' => $height]); }}<?php
class PageSize { public float $width = 0.0; public float $height = 0.0;
public function withDimensions(float $width, float $height): self { $__cloneResult1 = clone $this; $__cloneResult1->width = $width; $__cloneResult1->height = $height; return $__cloneResult1; }}The rule has documented limitations. Argument matching uses a non-recursive pattern, so override values with nested parentheses are not handled. Only string array keys are resolved to property names. Verified against rector/rules/DowngradeCloneWithRector.php and its fixture suite.
DowngradeTraitConstantsRector
Section titled “DowngradeTraitConstantsRector”Converts trait constants into static properties. Earlier runtimes reject trait constants with “Traits cannot have constants”. The rule also rewrites self::CONST and static::CONST references to the static-property form. Visibility is preserved; the final modifier is stripped because properties cannot be final on the older target. A typed class constant becomes a typed property. Source-backed transformation from 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; }}The two-pass PHP 7.4 pipeline
Section titled “The two-pass PHP 7.4 pipeline”The PHP 7.4 target cannot run in a single pass. Rector’s default-parameter-value resolver crashes on enum case defaults in constructor promotion. The build script therefore runs:
- Pass 1 — enum pre-processing.
rector-php74-enums.phpruns only the built-in enum-to-constant-list-class rule. After this pass, enum cases are plain class constants. - Cache clear. The Rector cache is cleared so the second pass does not see a stale tree.
- Post-process fix-ups.
scripts/build.phprewrites patterns the enum-to-class rule does not cover. Former enum instance methods become static.EnumClass::Case->valueand->nameaccesses are resolved. Named arguments that Rector could not bind are flattened to positional arguments. These syntax patterns would otherwise cause parse errors on PHP 7.4. - Pass 2 — full downgrade.
rector-php74.phpruns the full PHP 7.4 downgrade chain plus the custom rules.
Verified against scripts/build.php (runRector(), postProcessFixups()).
Build flag reference
Section titled “Build flag reference”scripts/build.php orchestrates the build. Its options are verified against the getopt() call and the Build constructor:
| Flag | Default | Effect |
|---|---|---|
--version=<x.y.z> | 2.0.0 | Version written into the generated composer.json and CHANGELOG.md |
--source-dir=<path> | c:/Users/admin/Documents | Root containing the sibling source repositories |
--output-dir=<path> | <repo>/output | Where the generated distribution is written |
--target=php74 | --target=php81 | php81 | Downgrade target. php74 forces core-only and disables Pro |
--dry-run | off | Run every stage in report-only mode; copies and Rector are skipped |
--no-pro | off | Exclude the Pro package (PHP 8.1 target only; PHP 7.4 already excludes it) |
An invalid --target value raises InvalidArgumentException before any work begins. Verified against Build::__construct() (VALID_TARGETS guard).
Composer scripts
Section titled “Composer scripts”| Script | Command | Purpose |
|---|---|---|
composer test | phpunit | Run the rule fixture suites |
composer analyse | phpstan analyse rector/rules scripts --level=10 | Static-analyse the build code |
composer build | php scripts/build.php | Full build |
composer build:dry | php scripts/build.php --dry-run | Dry-run build |
Verified against composer.jsonscripts.
- /integrations/backport/quickstart/ — run the dry-run and the full build.
- /integrations/backport/troubleshooting/ — what each failure stage means.