Loading...
Loading...
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.
npx skill4agent add peterfox/agent-skills phpstan-developerprocessNode()var_dump(get_class($node))Node::classgetNodeType()CollectedDataNodeRuleRuleTestCase<?php
declare(strict_types=1);
namespace App\PHPStan\Rules;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\Rules\IdentifierRuleError;
/**
* @implements Rule<MethodCall>
*/
final class MyRule implements Rule
{
public function getNodeType(): string
{
return MethodCall::class;
}
/**
* @param MethodCall $node
* @return list<IdentifierRuleError>
*/
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 | Effect |
|---|---|
| No errors — node is fine |
| Report one or more errors |
list<IdentifierRuleError>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->fixNode()phpstan analyse --fix@internal ExperimentalWhen the fix is complex, use Rector instead.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 thatfixNode()cannot. PHPStan finds the problem; Rector fixes it.fixNode()
RuleErrorBuilder::message('...')
->file('/path/to/file.php')
->line(42)
->identifier('myRule.something')
->build()$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 FQCNis*()has*()->yes()->no()->maybe()instanceofis*()$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<string> of class names
$type->getConstantStrings() // list<ConstantStringType>
$type->describe(VerbosityLevel::typeOnly()) // Human-readable type descriptiontests/Rules/MyRuleTest.php<?php
declare(strict_types=1);
namespace App\Tests\PHPStan\Rules;
use App\PHPStan\Rules\MyRule;
use PHPStan\Rules\Rule;
use PHPStan\Testing\RuleTestCase;
/**
* @extends RuleTestCase<MyRule>
*/
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'], []);
}
}tests/Rules/data/my-rule.php<?php
declare(strict_types=1);
namespace App\Tests\PHPStan\Rules\Data;
// This call should trigger the rule on line 10:
$obj->forbiddenMethod();data/analyse()getRule()rules:
- App\PHPStan\Rules\MyRuleservices:
-
class: App\PHPStan\Rules\MyRule
tags:
- phpstan.rules.rule
-
class: App\PHPStan\Collectors\MyCollector
tags:
- phpstan.collector