headercat/phpstan-extension-ide-helper
Provides dummy PHPStan namespace classes and functions so IDEs and autocompletion tools can reference PHPStan internals normally hidden inside its PHAR. Install as a dev dependency to get full autocompletion when writing PHPStan extensions.
Install it as a dev-only dependency, paired with the PHPStan version you actually run:
composer require --dev phpstan/phpstan:2.3.1 headercat/phpstan-extension-ide-helper:2.3.1
The helper's releases mirror PHPStan tags one-for-one (2.3.1 helper ↔ 2.3.1 PHPStan), so treat the version numbers as a single unit and pin both. Exact pins beat carets here: a floating helper against a floating analyser is how you end up with autocomplete describing classes your analyser doesn't have.
Where to look first
vendor/headercat/phpstan-extension-ide-helper/ — the generated stub tree described in the README as main/. Open any file: you'll see real namespaces and signatures, then an immediate return;. That's the whole package — it's a mirror of phpstan/phpstan-src with the bodies stripped.PHPStan\ resolves before writing code (see the smoke test below).phpstan/phpstan-src on GitHub at the matching tag. Stubs give you navigation; the source gives you behaviour. You will need both.First use case: writing a custom rule
The canonical entry point is a rule for something your team keeps getting wrong — in a Laravel app, an env() call outside config/, or raw SQL string interpolation. Drop this into a scratch file and check that autocompletion and go-to-definition work:
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
/** @implements Rule<NodeType> */
final class NoEnvOutsideConfig implements Rule
{
public function getNodeType(): string { /* ... */ }
public function processNode(Node $node, Scope $scope): array { /* ... */ }
}
If Rule, Scope, RuleErrorBuilder all resolve in your IDE, you're set up. Register the rule in phpstan.neon under rules: or as a tagged service, then validate with a real vendor/bin/phpstan analyse — never trust the IDE alone.
Keep the pair in lockstep, and enforce it. Put both in require-dev with identical pins and commit the lock file. On upgrade, bump PHPStan first, get a green analysis run, then bump the helper. That ordering isolates analyser breakage (which breaks CI) from stub breakage (which only breaks your DX), and stub breakage is much harder to notice.
Separate the extension from the app. If you write more than a rule or two, ship the rules as their own package (composer.json requiring phpstan/phpstan + the helper as dev deps) and pull it into the app via a path or VCS repository. Only people authoring rules pay the stub dependency; the app's dev install stays lean, and the version pairing lives in one place.
Two-window workflow. Go-to-definition in the IDE lands on a signature with no body. Keep phpstan-src checked out at the matching tag (or a browser tab on the tag) and read the implementation there. The stub's job is to tell you the class exists and what it takes — not what it does.
Autoload sanity check when something looks wrong:
php -r "var_dump(class_exists('PHPStan\\Rules\\Rule'), function_exists('PHPStan\\dumpType'));"
Both true in a dev install means the stubs are wired up and the problem is your editor's index, not Composer.
IDE configuration
vendor/ automatically; usually nothing to do. After a version bump, File → Invalidate Caches / Restart.intelephense.environment.includePaths and reindex. If symbols look stale or duplicated post-upgrade, clear Intelephense's index cache before touching anything else.Automate the version watch. The package regenerates from upstream on a ~30-minute cadence, but your project doesn't know that. A weekly scheduled Action that compares composer show phpstan/phpstan against the latest upstream release and opens an issue is enough — the failure mode here is silent drift, not loud breakage.
Stubs are declaration-only and will silently no-op if executed. Every file short-circuits with return; after the namespace. If one of these classes ever gets autoloaded at runtime, methods return null instead of exploding — a genuinely nasty class of bug. Keep the helper in require-dev, deploy with --no-dev, and never use a PHPStan\... class from application code.
Don't install it alongside phpstan/phpstan-src. Both claim the PHPStan\ namespace; you get duplicate/shadowed class definitions and an unpredictable winner. If you vendor phpstan-src for PHPUnit-based RuleTestCase tests, drop the helper. Note that RuleTestCase tests need the real classes at runtime — the PHAR isn't on Composer's autoloader, so either bootstrap phar://.../phpstan.phar/vendor/autoload.php in your phpunit bootstrap or run those tests inside a phpstan-src checkout.
Mismatched minors produce phantom APIs. A helper on 2.3.x against a 2.2.x analyser will happily autocomplete classes that don't exist, and signatures that don't match. The failure is invisible until CI rejects the rule — or worse, until a signature subtly differs and your rule misbehaves. Symptoms: "undefined class" in phpstan analyse for something your IDE insists is fine. Fix by re-pinning, not by suppressing.
Go-to-definition dead-ends are expected. Don't chase them; you were never meant to see bodies. Same for "unused parameter" squiggles on stub signatures — ignore anything inside vendor/headercat/.
The helper is not required for analysis. PHPStan knows its own internals when it analyses your rules, so the stubs buy you autocomplete and navigation only. That means you can safely delete the dependency on a branch to confirm it's the source of an IDE problem.
Release notes are empty. Each release is just a link to the upstream tag. Treat every bump as potentially breaking: diff phpstan-src between the two tags for renamed or moved internals, and grep your rules for FQCNs and inline @phpstan-* annotations before merging.
Coverage stops at PHPStan\ (plus phpstan-src's own phpstan/* Composer deps). Third-party extension base classes, and PHPStan's own phpstan.neon schema, aren't in the stubs. If a class you need isn't autocompleting after a bump, the helper is likely lagging upstream — check the tracking issue in the repo and request a regeneration, or pin to a helper release that matches your PHPStan tag.
If you run only a handful of rules, consider skipping it. The churn (two packages to bump in lockstep, IDE cache invalidation, silent version drift) can exceed the autocompletion benefit. PhpStorm users in particular often get adequate navigation by adding the PHAR to the include path directly.
How can I help you explore Laravel packages today?