nette/claude-code/phpstan-analysis
- community
Checking that the SKILL.md file is still in the repository.
Invoke BEFORE running PHPStan or fixing PHPStan errors. Covers error resolution strategy (refactoring > phpDoc > ignoring), common Nette error patterns, baseline management, and type tests. Use this whenever the user mentions PHPStan, static analysis, type errors, wants to suppress warnings, or manage the baseline - even for a single error.
Install
npx vskill@latest install nette/claude-code/phpstan-analysisSKILL.md
PHPStan Analysis
Running PHPStan
Never use --error-format=json - its output format can change between PHPStan versions and is not designed for stable machine consumption. For machine-readable output, use --error-format=raw.
Take the verdict from the exit code, never from grepping the output. A filter defines what you see, so anything it misses reads as success - and some findings (an unmatched ignoreErrors pattern, for one) are not tied to any file and carry no file prefix at all.
Target Levels
Target level for Nette libraries is 8. Levels higher than 8 are not worth pursuing - the additional strictness (e.g., non-empty-string, positive-int) catches very few real bugs relative to the annotation burden.
- Level 7: Union types checked
- Level 8: Null checks, strict types (our target)
nette/phpstan-rules
Installed by all Nette libraries. It works in both directions, and mixing them up is a real hazard:
- It narrows types and silences false positives — many PHPStan errors disappear without
manual fixes. Don't fight those (list below).
- It also REPORTS errors of its own and turns on stricter reporting. Two rules of its own:
nette.abortException (an AbortException caught and not rethrown — the fix is to rethrow it in its own catch, which is not guessable) and nette.strings.regexpPattern (an invalid regular expression). On top of that extension.neon forces nine flags on, including checkMissingCallableSignature, reportMaybesInMethodSignatures and reportAlwaysTrueInLastCondition — so some errors exist only because this extension is installed. Those are genuine findings; never silence them.
It also registers universalObjectCratesClasses for DefaultTemplate, Database\Row, SessionSection, SimpleIdentity and Html, so the most common property.notFound in a Nette app is already solved — don't add @property annotations for those.
Don't add asserts, casts, or @var for errors that fall into these categories:
- Nette helpers:
Strings::match(),Arrays::invoke(),Helpers::falseToNull(),Expect::array(),Htmlmagic methods (setXxx/getXxx/addXxx),Container::getComponent()and$this['name'], Form$form['name'] - Native PHP functions:
|false/|nullremoved where unrealistic (getcwd,json_encode,preg_*, intl/GD/DOM/etc.) - For
preg_*with theumodifier,falsecan genuinely occur on invalid UTF-8 input, yet it is stripped by design — a regex call site should not double as input validation. So a dead=== falsecheck afterpreg_*(identical.alwaysFalse) is not a bug in the extension: refactor to an explicit up-frontpreg_match('##u', $s)validation, the one form wherefalseis preserved. - After `Tester\Assert`:
notNull(),type(),true(), etc. narrow the type - Silenced false positives: arrow fns passed to
test()/Assert::exception(), runtime variadic-closure type validation, Form event-handler callbacks with narrow data parameter
Two features require config in phpstan.neon (NOT app's common.neon):
Database row mapping — narrows Explorer::table(), ActiveRow::related(), ::ref() to concrete row classes. Keys may contain a single * wildcard; a bare * is the catch-all and substitutes PascalCase of the table name into * in the value. Exact keys win over wildcards; wildcards are tried in declaration order.
Asset type narrowing — narrows Registry::getMapper() / getAsset() / tryGetAsset() (and FilesystemMapper::getAsset() / ViteMapper::getAsset()) based on mapper ID and file extension. Values file and vite are shortcuts for the built-in FilesystemMapper / ViteMapper; any other value is treated as an FQCN of a custom mapper class.
Full reference: Nette PHPStan Rules, bundled with the plugin.
Error Resolution Strategy
Resolution Priority
Resolve every error in this order of preference. Only fall back to the next step when the current one genuinely doesn't apply — this ladder is the backbone of the whole skill:
- Refactoring - if an error reveals a design weakness, fix the design first
- phpDoc - if the code is correct but its types are imprecise
- `assert()` - sparingly, only when the type cannot be expressed otherwise
- Ignore in `phpstan.neon` - for systematic or intentional patterns, always with a comment explaining why
- Baseline - last resort, keep minimal
Two hard rules override the ladder at every step:
- Never silence errors - a fix must not hide a potential problem (see "Never Silence Errors" below).
- Never use `@phpstan-ignore` annotations - keep checker-specific directives out of source code; ignore in
phpstan.neoninstead.
Agree on the Resolutions First
Before changing code, present the errors grouped by identifier, with the rung of the ladder chosen for each group and the reason.
Refactoring as First Choice
Always ask: does this error reveal a real design issue? Examples:
- Overly broad return types - method returns
mixedorobjectbut always returns a specific type; narrow the return type - Interface too loose - code calls a method on implementation but not on interface; extend the interface
- Mixed responsibilities - class handles too many types; split it
- Unnecessary dynamic access -
__get/__setwhere typed properties would work
The goal is not to "make PHPStan happy" but to use its feedback as a catalyst for better code.
Code Fixes Guidelines
Never Silence Errors
The code worked before. A fix that hides an error degrades code quality.
Better solutions, in order: use Json::encode(), or add an explicit check that throws. Only when neither applies, fall back to the baseline (last resort, per the resolution priority).
Throw Expression Pattern
Beware of /** @var */ in Method Bodies
/** @var Type */ in method body is taken authoritatively by PHPStan - it completely disables type checking for that variable. Use only when no better solution exists.
Don't "refine" bare callable into callable(...mixed): mixed
When PHPStan reports a missing callable signature (missingType.callable), it's tempting to write callable(...mixed): mixed. Don't. That type is narrower than bare callable, not wider, because of parameter contravariance:
callable(...mixed): mixedclaims the callee may be invoked with any arguments, so only callbacks whose parameters are allmixed-compatible (or which have no required typed params) satisfy it.- A normal callback like
function (UiForm $form, mixed $value): void {}is then rejected at call sites:expects callable(mixed...): mixed, Closure(UiForm, mixed): void given.
Bare callable is PHPStan's top type for callables — it accepts anything invokable regardless of signature, and $cb(...$args) inside the function still type-checks. So for "invoke arbitrary user callbacks" APIs keep callable (e.g. @param iterable<callable> $callbacks) and, if missingType.callable fires, ignore it for that file in phpstan.neon with a comment. An explicit signature buys nothing and introduces false positives. (nette/utils Arrays::invoke() is an example.)
The same applies to bare `\Closure` — when the value is guaranteed to be a closure (stored property, result of Closure::fromCallable(), etc.) but its signature is unknown or intentionally polymorphic, use plain \Closure without parameters. missingType.callable then fires on it too and is ignored on the same grounds.
phpDoc and Type Annotations
For phpDoc conventions (when to skip docs, array types, writing style), see the php-doc skill.
Key rules specific to PHPStan compatibility:
- phpDoc type must always match the native type:
| Native Type | Wrong phpDoc | Correct phpDoc | |||
|---|---|---|---|---|---|
| `array\ | string` | mixed[] | `mixed[]\ | string` | |
| `array\ | null` | int[] | `int[]\ | null` | |
| `object\ | array` | stdClass | `stdClass\ | array` |
- Don't use overly granular types (
positive-int,non-empty-string,non-empty-array,non-falsy-string) - they rarely catch real bugs but add significant annotation maintenance burden - Use
class-string<T>,array<string, Foo>,list<int>,array{name: string, age: int}- these are useful for PHPStan and worth maintaining
Array notation preference:
foo[]- always prefer for simple types (shortest notation)array<foo|bar>- for union types (more readable than(foo|bar)[])array<string, foo>orlist<foo>- when keys are not generic
Common Nette Error Patterns
property.nonObject - property access on array|object
In DI Extensions, $this->config returns array|object but is stdClass. This is one of the legitimate cases for /** @var */ in method body - the type cannot be expressed otherwise:
method.notFound / staticMethod.notFound / arguments.count
Calling method on interface that exists only on implementation. Fix type if possible, or use assert():
Note: when accessing components via $this['name'] or $this->getComponent('name') with a constant string and a matching createComponent<Name>() factory on the same class, phpstan-rules narrows the type automatically — no assert needed. The assert pattern above applies when the name is dynamic or the factory lives elsewhere.
property.uninitializedReadonly / property.readOnlyAssignNotInConstructor
Readonly properties initialized via inject methods (Nette DI pattern). Ignore - this is an intentional framework pattern.
new.static - unsafe usage of new static()
If intentional design pattern (derive/factory methods), ignore in phpstan.neon. Or change to new self if subclassing isn't expected.
closure.unusedUse
Variable in use ($var) used in require'd file. Ignore - false positive.
function.alreadyNarrowedType
PHPStan knows the type is already narrowed. Remove unnecessary condition, or ignore if it serves as runtime validation.
catch.neverThrown
Verify if the catch is actually needed. If so, ignore.
Ignoring Errors
Ignoring sits at the bottom of the resolution ladder (see "Resolution Priority") — exhaust refactoring, phpDoc, and assert() first. When ignoring is genuinely the right call, prefer phpstan.neon for systematic or intentional patterns (always with a comment) over phpstan-baseline.neon (last resort, minimize).
Forbidden and Discouraged Suppressions
These rules apply to both phpstan.neon ignoreErrors and phpstan-baseline.neon.
MUST NOT be suppressed — always fix:
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingplugins/nette-dev/skills/phpstan-analysis/SKILL.md
Popularity
- Repository stars
- 37
- Repository forks
- 4
- 7-day trend score
- 18
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 21 Mar 2026, 7 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/nette/claude-code/phpstan-analysis)