phpstan-analysis
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.
What this skill does
## PHPStan Analysis
### Running PHPStan
```bash
# Run with project configuration
vendor/bin/phpstan analyse
# Run on specific paths
vendor/bin/phpstan analyse src/foo/ src/bar.php
# Generate baseline for legacy projects
vendor/bin/phpstan analyse --generate-baseline
```
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`.
### 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. Transparently narrows types and silences false positives — many PHPStan errors disappear without manual fixes. Don't add asserts, casts, or `@var` for errors that fall into these categories:
- **Nette helpers**: `Strings::match()`, `Arrays::invoke()`, `Helpers::falseToNull()`, `Expect::array()`, `Html` magic methods (`setXxx`/`getXxx`/`addXxx`), `Container::getComponent()` and `$this['name']`, Form `$form['name']`
- **Native PHP functions**: `|false` / `|null` removed where unrealistic (`getcwd`, `json_encode`, `preg_*`, intl/GD/DOM/etc.)
- **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.
```neon
parameters:
nette:
database:
mapping:
tables:
booking: App\Entity\BookingRow # exact match
event_*: App\Entity\Event*Row # event_video → EventVideoRow
*: App\Entity\*Row # catch-all fallback
```
**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.
```neon
parameters:
nette:
assets:
mapping:
default: file # FilesystemMapper
images: file
vite: vite # ViteMapper
custom: App\MyMapper # custom mapper FQCN
```
Full reference: https://doc.nette.org/en/best-practices/phpstan-rules
---
## 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:
1. **Refactoring** - if an error reveals a design weakness, fix the design first
2. **phpDoc** - if the code is correct but its types are imprecise
3. **`assert()`** - sparingly, only when the type cannot be expressed otherwise
4. **Ignore in `phpstan.neon`** - for systematic or intentional patterns, always with a comment explaining why
5. **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.neon` instead.
### Create a Plan First
Before making any changes, create a plan:
1. **Group errors by type** (`property.nonObject`, `method.notFound`, `new.static`, etc.)
2. **For each type, choose a resolution** following the priority order above
3. **Justify each decision** with clear reasoning
4. **Present the plan** before implementing
### Refactoring as First Choice
Always ask: **does this error reveal a real design issue?** Examples:
- **Overly broad return types** - method returns `mixed` or `object` but 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`/`__set` where 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.
```php
// Before - json_encode returns false on error and we find out (type error)
function foo(): string {
return json_encode($this->value);
}
// WRONG - error is hidden
function foo(): string {
return (string) json_encode($this->value);
}
```
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
```php
// Before - fopen can return false
$f = fopen($file, 'r');
// Correct fix
$f = fopen($file, 'r') ?: throw new IOException("Cannot open file $file");
```
### 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): mixed` claims the callee may be invoked with *any* arguments, so only callbacks whose parameters are all `mixed`-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. (Confirmed in nette/utils `Arrays::invoke()`.)
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.
**Readability tip — wrap typed callables in parentheses.** When a callable/Closure type has a signature *and* appears in a union or alongside other type fragments, wrap it in `(...)` so the reader can see where the signature ends:
```php
// Hard to parse — does |null belong to the return type, or to the whole callable?
/** @var callable(): void|null $cb */
// Clear — the callable is one alternative, null is the other
/** @var (callable(): void)|null $cb */
// Same for Closure
/** @var (\Closure(int): string)|null $cb */
```
The parentheses are purely cosmetic for PHPStan (it parses both forms identically), but they save the reader from re-reading the line.
### Beware of `?:` operator with falsy values
The `?:Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.