Skip to content

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.

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.

FilePurposeUsed by
rector/config/rector-php81.phpSingle-pass downgrade to PHP 8.1PHP 8.1 target
rector/config/rector-php74-enums.phpPass 1 — convert enums to constant-list classesPHP 7.4 target
rector/config/rector-php74.phpPass 2 — full downgrade to PHP 7.4PHP 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/.

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 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.

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:

before
<?php
class Config {
public private(set) float $x = 0.0;
public private(set) string $name = '';
public private(set) int $count = 0;
}
after
<?php
class Config {
public float $x = 0.0;
public string $name = '';
public int $count = 0;
}

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:

before
<?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]);
}
}
after
<?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.

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:

before
<?php
trait HasLimit
{
private const MAX_SIZE = 1024;
public function getLimit(): int
{
return self::MAX_SIZE;
}
}
after
<?php
trait HasLimit
{
private static $MAX_SIZE = 1024;
public function getLimit(): int
{
return self::$MAX_SIZE;
}
}

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:

  1. Pass 1 — enum pre-processing. rector-php74-enums.php runs only the built-in enum-to-constant-list-class rule. After this pass, enum cases are plain class constants.
  2. Cache clear. The Rector cache is cleared so the second pass does not see a stale tree.
  3. Post-process fix-ups. scripts/build.php rewrites patterns the enum-to-class rule does not cover. Former enum instance methods become static. EnumClass::Case->value and ->name accesses 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.
  4. Pass 2 — full downgrade. rector-php74.php runs the full PHP 7.4 downgrade chain plus the custom rules.

Verified against scripts/build.php (runRector(), postProcessFixups()).

scripts/build.php orchestrates the build. Its options are verified against the getopt() call and the Build constructor:

FlagDefaultEffect
--version=<x.y.z>2.0.0Version written into the generated composer.json and CHANGELOG.md
--source-dir=<path>c:/Users/admin/DocumentsRoot containing the sibling source repositories
--output-dir=<path><repo>/outputWhere the generated distribution is written
--target=php74 | --target=php81php81Downgrade target. php74 forces core-only and disables Pro
--dry-runoffRun every stage in report-only mode; copies and Rector are skipped
--no-prooffExclude 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).

ScriptCommandPurpose
composer testphpunitRun the rule fixture suites
composer analysephpstan analyse rector/rules scripts --level=10Static-analyse the build code
composer buildphp scripts/build.phpFull build
composer build:dryphp scripts/build.php --dry-runDry-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.