Skip to content

Commit d81f413

Browse files
committed
CLAUDE.md file
1 parent d56d189 commit d81f413

1 file changed

Lines changed: 214 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
# PHPStan - PHP Static Analysis Tool
2+
3+
PHPStan finds bugs in PHP code without running it. It analyses the entire codebase and reports type errors, undefined methods/properties/variables, dead code, incorrect PHPDoc types, and many other issues. It understands PHP's type system deeply, including generics, union/intersection types, literal types, conditional return types, and template types expressed through PHPDocs.
4+
5+
## Key concepts
6+
7+
- **Rule levels 0-10**: Incremental adoption from basic checks (level 0) to strict mixed type enforcement (level 10). Levels are cumulative. Level 0 covers unknown classes/functions/methods and undefined variables. Level 5 checks argument types. Level 6 requires typehints. Level 9 enforces explicit `mixed`. Level 10 enforces implicit `mixed`.
8+
- **Baseline**: Allows adopting higher rule levels by recording existing errors in a baseline file, so only new errors are reported.
9+
- **Bleeding edge**: Preview of next major version features, shipped in current stable release via `bleedingEdge.neon`.
10+
- **Result cache**: PHPStan caches analysis results and only re-analyses changed files and their dependents.
11+
- **Parallel analysis**: Files are analysed in parallel across multiple child processes using React PHP.
12+
- **Configuration**: NEON format (Nette configuration). Main config is `phpstan.neon`, level configs in `conf/config.level*.neon`, services in `conf/services.neon`.
13+
14+
## Running PHPStan
15+
16+
```bash
17+
# Analyse with a specific level
18+
vendor/bin/phpstan analyse -l 8 src tests
19+
20+
# Clear result cache
21+
vendor/bin/phpstan clear-result-cache
22+
23+
# Generate baseline
24+
vendor/bin/phpstan analyse --generate-baseline
25+
26+
# Debug mode (shows which files are being analysed)
27+
vendor/bin/phpstan analyse --debug
28+
```
29+
30+
## Running tests
31+
32+
```bash
33+
vendor/bin/phpunit
34+
```
35+
36+
Rules are tested using `PHPStan\Testing\RuleTestCase`, type extensions with `PHPStan\Testing\TypeInferenceTestCase`.
37+
38+
## Architecture
39+
40+
The codebase lives under `src/` with PSR-4 autoloading mapping `PHPStan\` to `src/`. Key architectural components:
41+
42+
### PHPStan\Analyser - Core analysis engine
43+
44+
The analysis pipeline: `Analyser` orchestrates `FileAnalyser`, which uses `NodeScopeResolver` to walk the AST and invoke registered `Rule` implementations.
45+
46+
**`NodeScopeResolver`** (`src/Analyser/NodeScopeResolver.php`) - The central engine of PHPStan. It traverses the AST (from nikic/php-parser) and maintains a `MutatingScope` that gets updated at each node. It handles:
47+
- Control flow analysis (if/else, switch, try/catch, loops, match)
48+
- Variable assignments and type narrowing
49+
- Function/method call resolution
50+
- Closure and arrow function scoping
51+
- Type narrowing from conditions (`instanceof`, `is_*()`, `===`, etc.)
52+
- PHPDoc type resolution and `@var`/`@param`/`@return` processing
53+
- Invoking registered Rules and Collectors at each AST node
54+
55+
**`MutatingScope`** (`src/Analyser/MutatingScope.php`) - Holds the current state of analysis after each AST node. It tracks:
56+
- Variable types (assigned, possibly-defined, narrowed)
57+
- Current context: namespace, class, method/function, trait, anonymous function
58+
- Property types and initialization state
59+
- Constant values
60+
- Expression types via `getType(Expr $expr): Type`
61+
- Native types vs PHPDoc types
62+
63+
The `Scope` interface (`src/Analyser/Scope.php`) is the public API that rules and extensions use. `MutatingScope` is the internal implementation.
64+
65+
**`TypeSpecifier`** (`src/Analyser/TypeSpecifier.php`) - Narrows types based on conditions. When NodeScopeResolver processes an `if ($x instanceof Foo)`, TypeSpecifier determines that `$x` is `Foo` in the truthy branch and `not Foo` in the falsy branch. It uses `TypeSpecifierContext` (truthy/falsey/true/false/null) to track which branch is being processed. Extensible via `FunctionTypeSpecifyingExtension`, `MethodTypeSpecifyingExtension`, and `StaticMethodTypeSpecifyingExtension`.
66+
67+
**`ExpressionTypeHolder`** (`src/Analyser/ExpressionTypeHolder.php`) - Stores an expression's type together with a `TrinaryLogic` certainty (Yes = definitely this type, Maybe = possibly this type). This is the building block of how `MutatingScope` tracks variable types - it maps expression keys to `ExpressionTypeHolder` instances.
68+
69+
**`ConstantResolver`** (`src/Analyser/ConstantResolver.php`) - Resolves PHP constant names to their types. Handles 150+ predefined PHP constants with PHP-version-aware types (e.g. different return types based on `PHP_VERSION_ID`). Also resolves user-configured constant type mappings.
70+
71+
**`InitializerExprTypeResolver`** (`src/Reflection/InitializerExprTypeResolver.php`) - Resolves types of constant expressions and initializers (default parameter values, property defaults, constant values). Handles arithmetic, casts, function calls, and binary operations in constant contexts where a full Scope is not available.
72+
73+
### Analysis pipeline in detail
74+
75+
1. `AnalyseCommand` parses CLI arguments, builds the DI container
76+
2. `Analyser` receives the list of files, sets up `NodeScopeResolver` with the full file list
77+
3. For parallel runs, `ParallelAnalyser` distributes files across worker processes via TCP using NDJSON protocol (React event loop). Default: up to 32 processes, 20 files per job, 600s timeout.
78+
4. Each worker runs `FileAnalyser::analyseFile()` which parses a file to AST, then calls `NodeScopeResolver::processStmtNodes()`
79+
5. `NodeScopeResolver` walks the AST depth-first. At each node, it updates `MutatingScope` and invokes all registered `Rule` and `Collector` callbacks for that node type
80+
6. Results (errors, dependencies, collected data) flow back to the main process
81+
7. After all files are processed, `CollectedDataNode` rules run with the aggregated collector data
82+
8. `ResultCacheManager` saves results keyed by file SHA256 hashes and a dependency graph for incremental re-analysis
83+
84+
### Result cache (`src/Analyser/ResultCache/`)
85+
86+
`ResultCacheManager` enables incremental analysis. It tracks:
87+
- SHA256 hashes of all analysed files
88+
- Dependency graph between files (so changing one file re-analyses its dependents)
89+
- Exported nodes (class/function signatures) to detect API changes
90+
- PHP version, loaded extensions, and config hash for cache invalidation
91+
92+
On subsequent runs, only changed files and their transitive dependents are re-analysed.
93+
94+
### Error ignoring (`src/Analyser/Ignore/`)
95+
96+
`IgnoredErrorHelper` processes error ignore patterns from configuration and inline `@phpstan-ignore` comments. Supports regex patterns, error identifiers, and file-specific ignoring. Tracks which ignore patterns were matched so unmatched patterns can be reported.
97+
98+
### PHPStan\Type - Type system
99+
100+
Implementations of the `Type` interface (`src/Type/Type.php`) represent everything PHPStan knows about types. Each type knows:
101+
- What it accepts (`accepts()`) and what is a supertype of it (`isSuperTypeOf()`)
102+
- What properties/methods/constants it has
103+
- What operations result in what types (array operations, arithmetic, string operations, etc.)
104+
- How to describe itself for error messages (`describe()`)
105+
- How to narrow itself (`tryRemove()`, generalize, traverse)
106+
107+
Key type classes:
108+
- `ObjectType`, `StringType`, `IntegerType`, `FloatType`, `BooleanType`, `NullType`, `ArrayType`, `MixedType`, `NeverType`, `VoidType`
109+
- `UnionType`, `IntersectionType` - composite types
110+
- `Constant\ConstantStringType`, `Constant\ConstantIntegerType`, `Constant\ConstantArrayType` - literal/known values
111+
- `Generic\GenericObjectType`, `Generic\TemplateType` - generics
112+
- `Accessory\AccessoryNonEmptyStringType`, `Accessory\NonEmptyArrayType`, etc. - combined via intersection for refined types like `non-empty-string`
113+
- `IntegerRangeType` - integer ranges like `int<0, 100>`
114+
- `Enum\EnumCaseObjectType` - enum cases
115+
- `ClosureType`, `CallableType` - callable types
116+
- `StaticType`, `ThisType` - late static binding
117+
118+
**`TypeCombinator`** - Used instead of constructing `UnionType`/`IntersectionType` directly. Handles type normalization (e.g. `mixed|int` becomes `mixed`, `string&int` becomes `never`).
119+
120+
**`TrinaryLogic`** - Three-valued logic (yes/no/maybe) used throughout the type system. Many Type methods return `TrinaryLogic` instead of `bool` because type relationships aren't always certain (e.g. `mixed` might be a string - that's `maybe`).
121+
122+
To query whether a type is a specific type, use `isSuperTypeOf()`, not `instanceof`. For example, `(new StringType())->isSuperTypeOf($type)->yes()` correctly handles union types, intersection types with accessory types, etc. There are also shortcut methods like `$type->isString()`, `$type->isInteger()`, etc.
123+
124+
### PHPStan\Rules - Static analysis checks
125+
126+
Rules implement the `Rule<TNodeType>` interface (`src/Rules/Rule.php`):
127+
- `getNodeType(): string` - returns the AST node class to listen for
128+
- `processNode(Node $node, Scope $scope): array` - returns errors found at this node
129+
130+
Rules are organized into subdirectories by category: `Classes/`, `Methods/`, `Properties/`, `Functions/`, `Variables/`, `DeadCode/`, `Generics/`, `PhpDoc/`, `Cast/`, `Comparison/`, `Exceptions/`, `Pure/`, `Arrays/`, `Types/`, etc.
131+
132+
Rules are registered in configuration via the `phpstan.rules.rule` service tag or the `rules:` section. Different rule levels activate different rules via `conf/config.level*.neon`.
133+
134+
**Collectors** (`src/Collectors/`) - For rules that need cross-file information (e.g. unused code detection). Collectors gather data across all files in parallel processes, then a rule registered for `CollectedDataNode` processes the aggregated data.
135+
136+
### PHPStan\Reflection - Code metadata
137+
138+
PHPStan has its own reflection layer, primarily backed by the BetterReflection library (`ondrejmirtes/better-reflection`), which provides static reflection (reading code without loading it).
139+
140+
**`ReflectionProvider`** (`src/Reflection/ReflectionProvider.php`) - Central entry point for looking up classes, functions, and constants.
141+
142+
**`ClassReflection`** (`src/Reflection/ClassReflection.php`) - Represents classes, interfaces, traits, and enums. Provides access to methods, properties, constants, parent classes, interfaces, traits, generics, PHPDocs, and attributes.
143+
144+
**Class reflection extensions** allow describing magic properties/methods from `__get`/`__set`/`__call`.
145+
146+
The reflection layer also includes `ParametersAcceptor` for function/method signatures (with multi-variant support for overloaded built-in functions), `SignatureMap` for built-in PHP function signatures, and stub files for overriding third-party type information.
147+
148+
### PHPStan\Node - Virtual AST nodes
149+
150+
PHPStan augments the nikic/php-parser AST with custom virtual nodes (`src/Node/`):
151+
- `FileNode` - wraps an entire file
152+
- `InClassNode`, `InClassMethodNode`, `InFunctionNode`, `InTraitNode` - provide scope-aware context (e.g. `InClassNode` lets rules access `$scope->getClassReflection()`)
153+
- `ClassPropertiesNode`, `ClassMethodsNode`, `ClassConstantsNode` - aggregate all properties/methods/constants of a class (useful for checking completeness)
154+
- `ClassPropertyNode` - unifies traditional and promoted properties
155+
- `CollectedDataNode` - carries aggregated data from collectors
156+
- `ExecutionEndNode` - marks unreachable code points
157+
- `MatchExpressionNode`, `BooleanAndNode`, `BooleanOrNode` - enhanced representations of expressions
158+
159+
### PHPStan\PhpDoc - PHPDoc parsing
160+
161+
Uses `phpstan/phpdoc-parser` to parse PHPDoc comments into an AST, then resolves PHPDoc types into `PHPStan\Type\Type` objects via `TypeNodeResolver`. Handles `@param`, `@return`, `@var`, `@throws`, `@template`, `@extends`, `@implements`, `@phpstan-assert`, `@phpstan-type`, `@phpstan-import-type`, conditional return types, and more.
162+
163+
### PHPStan\DependencyInjection - Service container
164+
165+
Uses Nette DI container. Services are configured in NEON files. Extensions register via service tags like `phpstan.rules.rule`, `phpstan.broker.dynamicMethodReturnTypeExtension`, `phpstan.typeSpecifier.functionTypeSpecifyingExtension`, `phpstan.collector`, etc.
166+
167+
The `#[AutowiredService]` and `#[AutowiredParameter]` attributes are used for automatic service registration.
168+
169+
### PHPStan\Node\ClassStatementsGatherer
170+
171+
Collects structural information about a class during AST traversal: properties, methods, method calls, property reads/writes. Used by virtual nodes like `ClassPropertiesNode` and `ClassMethodsNode` to provide aggregate information for rules that check class-level invariants (e.g. unused private properties, uninitialized properties).
172+
173+
### PHPStan\Fixable - Auto-fixing
174+
175+
`Patcher` coordinates automatic error fixes. Rules can attach fix information to errors via `RuleErrorBuilder`. The `PhpPrinter` handles code generation, `ReplacingNodeVisitor` performs AST node replacements, and indentation is preserved via `PhpPrinterIndentationDetectorVisitor`.
176+
177+
### Other components
178+
179+
- **`PHPStan\Parser`** - Wraps nikic/php-parser with caching, visitor registration, and PHPStan-specific AST enrichment (e.g. `ArrayMapArgVisitor`, `ImmediatelyInvokedClosureVisitor`)
180+
- **`PHPStan\Parallel`** - `ParallelAnalyser` + `Scheduler` + `ProcessPool` distribute file analysis across child processes via React TCP server
181+
- **`PHPStan\Command`** - CLI commands (`AnalyseCommand`, `ClearResultCacheCommand`, etc.) and error formatters (table, json, github actions, etc.)
182+
- **`PHPStan\Dependency`** - Tracks file dependencies for incremental analysis / result cache. `ExportedNode` represents a class/function/constant signature for detecting API changes.
183+
- **`PHPStan\File`** - File path resolution and reading
184+
- **`PHPStan\Php`** - `PhpVersion` abstraction with version source tracking (runtime, config, composer platform). Methods like `supportsEnums()`, `supportsReadonlyProperties()`, etc. for version-specific behavior.
185+
- **`PHPStan\Cache`** - Caching infrastructure
186+
- **`PHPStan\Testing`** - `RuleTestCase`, `TypeInferenceTestCase`, and other test utilities
187+
188+
## Extension points
189+
190+
PHPStan is highly extensible. Key extension interfaces:
191+
192+
- **Custom rules** - `PHPStan\Rules\Rule` interface, tag: `phpstan.rules.rule`
193+
- **Dynamic return type extensions** - `DynamicMethodReturnTypeExtension`, `DynamicStaticMethodReturnTypeExtension`, `DynamicFunctionReturnTypeExtension`
194+
- **Type-specifying extensions** - `MethodTypeSpecifyingExtension`, `StaticMethodTypeSpecifyingExtension`, `FunctionTypeSpecifyingExtension` - for custom type narrowing (like `is_int()`)
195+
- **Class reflection extensions** - `PropertiesClassReflectionExtension`, `MethodsClassReflectionExtension` - for magic properties/methods
196+
- **Dynamic throw type extensions** - describe when functions throw based on arguments
197+
- **Closure type extensions** - override closure parameter/return types or `$this` binding
198+
- **Custom PHPDoc types** - `TypeNodeResolverExtension` for custom type syntax
199+
- **Collectors** - `PHPStan\Collectors\Collector` for cross-file analysis
200+
- **Error formatters** - custom output formats
201+
- **Restricted usage extensions** - simple interfaces to restrict where methods/properties/functions can be called from
202+
- **Allowed subtypes** - define sealed class hierarchies
203+
- **Always-read/written properties, always-used constants/methods** - suppress false positives for dead code detection
204+
205+
## Important dependencies
206+
207+
- `nikic/php-parser` ^5.7.0 - PHP AST parsing
208+
- `ondrejmirtes/better-reflection` - Static reflection (reading code without loading it)
209+
- `phpstan/phpdoc-parser` - PHPDoc parsing
210+
- `nette/di` - Dependency injection container
211+
- `nette/neon` - Configuration file format
212+
- `react/child-process`, `react/async` - Parallel analysis
213+
- `symfony/console` - CLI interface
214+
- `hoa/compiler` - Used for regex type parsing

0 commit comments

Comments
 (0)