AgentStack
SKILL verified MIT Self-run

Rector Developer

skill-peterfox-agent-skills-rector-developer · by peterfox

Build Rector PHP rules that transform PHP code via AST. Use when asked to create, modify, or explain Rector rules for PHP code transformations. Rector rules use the PHP-Parser AST and PHPStan type analysis. Triggers on requests like "write a Rector rule to...", "create a Rector rule that...", "add a Rector rule for...", or when working in a rector-src or rector-based project and asked to implemen…

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-peterfox-agent-skills-rector-developer

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Rector Developer? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Rector PHP Rule Builder

Rector transforms PHP code by traversing the PHP-Parser AST, matching node types, and returning modified nodes from refactor().

Workflow

  1. Check for an existing configurable rule first — see references/configurable-rules.md. Renaming functions/methods/classes, converting call types, and removing arguments are all covered. Prefer ->withConfiguredRule() over writing a custom rule for these cases.
  2. Identify the PHP-Parser node type(s) to target (see references/node-types.md)
  3. Write the rule class extending AbstractRector
  4. If PHP version gated, implement MinPhpVersionInterface
  5. If configurable, implement ConfigurableRectorInterface
  6. Register the rule in rector.php config

Rule Skeleton

> */
    public function getNodeTypes(): array
    {
        return [FuncCall::class];
    }

    /** @param FuncCall $node */
    public function refactor(Node $node): ?Node
    {
        if (! $this->isName($node, 'target_function')) {
            return null;
        }

        // transform and return modified $node, or return null for no change
        return $node;
    }
}

refactor() Return Values

| Return | Effect | |--------|--------| | null | No change, continue traversal | | $node (modified) | Replace with modified node | | Node[] (non-empty) | Replace with multiple nodes | | NodeVisitor::REMOVE_NODE | Delete the node |

Never return an empty array — throws ShouldNotHappenException.

Protected Methods on AbstractRector

// Name checking
$this->isName($node, 'functionName')         // exact name match
$this->isNames($node, ['name1', 'name2'])    // match any
$this->getName($node)                         // get name string or null

// Type checking (PHPStan-powered)
$this->getType($node)                         // returns PHPStan Type
$this->isObjectType($node, new ObjectType('ClassName'))

// Traversal
$this->traverseNodesWithCallable($nodes, function (Node $node): int|Node|null {
    return null; // continue
    // or return NodeVisitor::STOP_TRAVERSAL;
    // or return NodeVisitor::DONT_TRAVERSE_CURRENT_AND_CHILDREN;
});

// Misc
$this->mirrorComments($newNode, $oldNode);    // copy comments

Creating Class Name Nodes

Always use Node\Name\FullyQualified for class references in AST nodes — never Node\Name. The string must not have a leading backslash. See references/node-types.md (Creating Class Name Nodes) for the full list of affected node properties.

Preventing Duplicate Attributes

When adding PHP attributes, use PhpAttributeAnalyzer (inject via constructor) to check if the attribute is already present. Guard non-repeatable attributes with an early return null; for repeatable attributes, only guard when the specific instance you'd add is already there. Always add a skip_attribute_already_present.php.inc fixture for non-repeatable attributes.

See references/helpers.md (PhpAttributeAnalyzer section) for injection, method signatures, and repeatability guidance.

Reducing Rule Risk

Before transforming a class or its members, consider whether the change is safe in an inheritance context. Rector rules run against arbitrary codebases, so a transformation that looks correct on a standalone class may break subclasses or consumers.

Non-final classes

If the class being transformed is not final, it may be extended. A rule that adds, removes, or changes a method/property/constant on a non-final class could silently break subclasses (e.g. method signature change, new abstract requirement, changed return type).

Ask: could subclasses be affected by this transformation?

  • If yes and the risk is real, guard with isFinal() and skip non-final classes. Add a skip_non_final_class.php.inc fixture.
  • If the rule is intentionally broad and the risk is accepted, document that reasoning in the rule's getRuleDefinition() description.
  • Some rules legitimately target non-final classes (e.g. adding a type declaration to a public method) — in those cases consider whether it's safe to apply on public/protected members (see below).
// Skip if class is not final
$classNode = $this->betterNodeFinder->findParentType($node, Class_::class);
if (! $classNode instanceof Class_) {
    return null;
}
if (! $classNode->isFinal()) {
    return null;
}

Public and protected members

Public and protected methods, properties, and constants form the class's API contract — both for external callers and for subclasses. Changing them (renaming, adding/removing parameters, changing types, adding attributes) carries more risk than changing private members.

Ask: is this member public or protected?

  • private members — safe to transform; no external or inheritance contract.
  • protected members — subclasses may override or depend on the original signature. Consider skipping, or at minimum add skip fixtures for protected cases.
  • public members — broadest risk. Weigh whether the rule should be limited to private, opt-in via configuration, or require the class to be final.
// Example: only transform private methods
if (! $node->isPrivate()) {
    return null;
}

// Example: skip public/protected properties
if ($node->isPublic() || $node->isProtected()) {
    return null;
}

Injected Services

Inject via constructor (autowired by DI container):

public function __construct(
    private readonly BetterNodeFinder $betterNodeFinder,
    // ... other services
) {}
  • $this->nodeFactory — create nodes (see references/helpers.md)
  • $this->nodeComparator — compare nodes structurally
  • $this->betterNodeFinder — search within nodes (inject via constructor)
  • PHPDoc manipulation: inject PhpDocInfoFactory + DocBlockUpdater

Configurable Rules

use Rector\Contract\Rector\ConfigurableRectorInterface;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\ConfiguredCodeSample;

final class MyRector extends AbstractRector implements ConfigurableRectorInterface
{
    private string $targetClass = 'OldClass';

    public function configure(array $configuration): void
    {
        $this->targetClass = $configuration['target_class'] ?? $this->targetClass;
    }

    public function getRuleDefinition(): RuleDefinition
    {
        return new RuleDefinition('...', [
            new ConfiguredCodeSample('before', 'after', ['target_class' => 'OldClass']),
        ]);
    }
}

PHP Version Gating

use Rector\VersionBonding\Contract\MinPhpVersionInterface;
use Rector\ValueObject\PhpVersionFeature;

final class MyRector extends AbstractRector implements MinPhpVersionInterface
{
    public function provideMinPhpVersion(): int
    {
        return PhpVersionFeature::ENUM; // PHP 8.1+
    }
}

See references/php-versions.md for all PhpVersionFeature constants.

rector.php Registration

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withRules([MyRector::class])
    // configurable rule:
    ->withConfiguredRule(MyConfigurableRector::class, ['key' => 'value']);

Namespace Convention

Rules live at: rules/[Category]/Rector/[NodeType]/[RuleName]Rector.php Tests live at: rules-tests/[Category]/Rector/[NodeType]/[RuleName]Rector/

Categories: CodeQuality, CodingStyle, DeadCode, EarlyReturn, Naming, Php52Php85, Privatization, Removing, Renaming, Strict, Transform, TypeDeclaration

Writing Tests

Every rule needs a test class extending AbstractRectorTestCase with fixtures in a Fixture/ directory and a config in config/configured_rule.php.

Fixture tip: Write only the input section, run the test, and FixtureFileUpdater fills the expected output automatically.

Skip fixtures: One skip_*.php.inc file per no-change scenario — single section, no ----- separator.

See references/testing.md for the full test class template, fixture format, config file formats, configurable rule variants, and special cases.

Reference Files

  • references/configurable-rules.md — All built-in configurable rules with config examples (check this before writing a custom rule)
  • references/node-types.md — PhpParser node type quick reference (FuncCall, MethodCall, Class_, etc.)
  • references/helpers.md — NodeFactory methods, BetterNodeFinder, NodeComparator, PhpDocInfo
  • references/php-versions.md — PhpVersionFeature constants by PHP version
  • references/testing.md — Full test structure, fixture format, configurable rule testing, special cases

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.