AgentStack
SKILL verified MIT Self-run

Phpstan Developer

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

Build PHPStan rules, collectors, and extensions that analyze PHP code for custom errors. Use when asked to create, modify, or explain PHPStan rules, collectors, or type extensions. Triggers on requests like "write a PHPStan rule to...", "create a PHPStan rule that...", "add a PHPStan rule for...", "write a collector for...", or when working on a phpstan extension package.

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

Install

$ agentstack add skill-peterfox-agent-skills-phpstan-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 Phpstan Developer? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

PHPStan Extension Builder

PHPStan finds bugs by traversing the PHP-Parser AST, resolving types via PHPStan's type system, and reporting errors from processNode().

Workflow

  1. Identify the PHP-Parser node type to target — use var_dump(get_class($node)) with Node::class as a temporary getNodeType() to discover node types, or check the php-parser docs
  2. For cross-file analysis (e.g. "find unused things", "check all calls to X"), use a Collector to gather data and a CollectedDataNode rule to report — see references/collectors.md
  3. Write the Rule class extending nothing — implement Rule interface directly
  4. Write the test class extending RuleTestCase with fixture PHP files
  5. Register the rule in a neon config file

Rule Skeleton


 */
final class MyRule implements Rule
{
    public function getNodeType(): string
    {
        return MethodCall::class;
    }

    /**
     * @param MethodCall $node
     * @return list
     */
    public function processNode(Node $node, Scope $scope): array
    {
        // Return [] for no error, or build errors:
        return [
            RuleErrorBuilder::message('Something is wrong.')
                ->identifier('myRule.something')  // required: camelCase.dotSeparated
                ->build(),
        ];
    }
}

processNode() Return Values

| Return | Effect | |--------|--------| | [] | No errors — node is fine | | [RuleErrorBuilder::...->build()] | Report one or more errors |

Return type is always list. Never return a single object — always wrap in an array.

RuleErrorBuilder API

RuleErrorBuilder::message('Error message text.')   // required
    ->identifier('category.specific')              // required; pattern: /[a-z][a-z0-9]*(\.[a-z0-9]+)*/
    ->line($node->getStartLine())                  // override line number
    ->tip('Suggestion to fix this.')               // optional tip shown to user
    ->addTip('Additional tip.')                    // add more tips
    ->discoveringSymbolsTip()                      // standard "class not found" tip
    ->nonIgnorable()                               // cannot be suppressed with @phpstan-ignore
    ->fixNode($node, fn (Node $n) => $modified)   // experimental: provide an automatic fix
    ->build()                                      // returns IdentifierRuleError

Fixable errors->fixNode() attaches an AST transformation callable to the error. When the user runs phpstan analyse --fix (or their editor's PHPStan integration applies fixes), PHPStan replaces the original node with the result of the callable. The callable receives the original node and must return a replacement node of the same type. This is marked @internal Experimental in the source but is used throughout PHPStan core. See references/testing.md for how to test fixes.

> When the fix is complex, use Rector instead. fixNode() is limited to replacing a single node in-place. If the fix needs to add imports, restructure multiple nodes, move code, or make changes across more than one location in the file, write a Rector rule instead. Rector is purpose-built for multi-step AST transformations and handles pretty-printing, import resolution, and edge cases that fixNode() cannot. PHPStan finds the problem; Rector fixes it.

For CollectedDataNode rules (cross-file), you must set file and line explicitly:

RuleErrorBuilder::message('...')
    ->file('/path/to/file.php')
    ->line(42)
    ->identifier('myRule.something')
    ->build()

Common Scope Methods

$scope->getType($node)                    // Type of any Expr node
$scope->isInClass()                       // Currently inside a class?
$scope->getClassReflection()              // ClassReflection|null
$scope->getFunction()                     // FunctionReflection|null
$scope->isInAnonymousFunction()           // Inside a closure?
$scope->hasVariableType('varName')        // TrinaryLogic: yes/maybe/no
$scope->getVariableType('varName')        // Type of $varName
$scope->filterByTruthyValue($expr)        // Narrowed scope when $expr is true
$scope->isDeclareStrictTypes()            // strict_types=1 active?
$scope->resolveName($nameNode)            // Resolve self/parent/static to FQCN

TrinaryLogic — the result of all is*() and has*() checks. Has three states:

  • ->yes() — definitely true; use when you want zero false positives
  • ->no() — definitely false; use as an early-return guard to skip inapplicable nodes
  • ->maybe() — uncertain (mixed/union); use for softer warnings or combined checks

See references/trinary-logic.md for the full decision guide, logical operations, and patterns.

Common Type Methods

Never use instanceof on PHPStan types — always use the is*() methods:

$type = $scope->getType($node);

$type->isString()->yes()         // Is definitely a string?
$type->isObject()->yes()         // Is definitely an object?
$type->isNull()->yes()           // Is always null?
$type->isArray()->yes()          // Is always an array?
$type->getObjectClassNames()     // list of class names
$type->getConstantStrings()      // list
$type->describe(VerbosityLevel::typeOnly())  // Human-readable type description

Writing Tests

Every rule needs a test class and at least one fixture file. Use one fixture file per scenario.

Test class (tests/Rules/MyRuleTest.php):


 */
final class MyRuleTest extends RuleTestCase
{
    protected function getRule(): Rule
    {
        return new MyRule();
    }

    public function testRule(): void
    {
        $this->analyse(
            [__DIR__ . '/data/my-rule.php'],
            [
                ['Error message text.', 10],       // [message, line]
                ['Another error.', 25, 'A tip.'],  // [message, line, tip] (optional)
            ]
        );
    }

    public function testNoErrors(): void
    {
        $this->analyse([__DIR__ . '/data/my-rule-clean.php'], []);
    }
}

Fixture file (tests/Rules/data/my-rule.php) — plain PHP file with code that triggers the rule:

forbiddenMethod();

Key rules:

  • One scenario per fixture file — do not mix multiple unrelated scenarios in one file
  • Fixture files live in a data/ subdirectory relative to the test class
  • The analyse() assertion fails if any unexpected errors appear, or expected errors are missing
  • If a rule has constructor dependencies, create them manually in getRule()

See references/testing.md for: additional config files, injecting services, TypeInferenceTestCase.

Registration (phpstan.neon / extension.neon)

Shorthand (simple rules with no constructor dependencies):

rules:
    - App\PHPStan\Rules\MyRule

Full service registration (for rules with dependencies):

services:
    -
        class: App\PHPStan\Rules\MyRule
        tags:
            - phpstan.rules.rule

    -
        class: App\PHPStan\Collectors\MyCollector
        tags:
            - phpstan.collector

Reference Files

  • references/trinary-logic.md — TrinaryLogic in depth: when to use yes/no/maybe, and/or/negate, patterns
  • references/collectors.md — Collector interface, cross-file analysis, CollectedDataNode pattern
  • references/testing.md — Full test structure, injecting services, additional config files, TypeInferenceTestCase
  • references/scope-api.md — Full Scope API, ReflectionProvider, ClassReflection methods
  • references/virtual-nodes.md — PHPStan virtual nodes (InClassNode, InClassMethodNode, FileNode, etc.)
  • references/extensions.md — Dynamic return type extensions, type specifying extensions, reflection extensions, neon service tags

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.