PhpStorm's Built-In Analysis vs PHPStan/Psalm: A Practical Layering Strategy
PhpStorm's on-the-fly inspections and PHPStan/Psalm serve different purposes. Layer them: IDE for instant syntactic feedback, CLI for whole-program guarantees. Disable auto-runs to avoid UI lag; use Remote Development for large codebases.
05 Nov 2026, 09:23 UTC

The Problem: Two Engines, One Codebase
You're writing PHP in PhpStorm. The IDE underlines a variable in red — type mismatch. You fix it. The underline disappears. You commit. CI runs PHPStan at level 6 and fails: the same method returns User|null but your caller assumes User. The IDE didn't catch it because it only sees the method signature, not the nullability flow across the repository layer.
This isn't a bug in either tool. PhpStorm's on-the-fly inspections and PHPStan/Psalm operate on different type systems with different goals. The practical question isn't which to pick — it's how to layer them so you get fast feedback while coding and architectural guarantees in CI without duplicating effort or slowing your machine.
How the Engines Differ
PhpStorm's inspections run inside the IDE process. They use a custom type inference engine that updates as you type — no build step, no CLI. They're excellent at syntactic problems: missing imports, unused parameters, @param/@return mismatches, and framework-specific rules (Symfony routes, Laravel facades, Doctrine entities). Because they index incrementally, they surface typos and obvious contract violations instantly.
PHPStan and Psalm run as standalone CLI tools. They perform whole-program analysis: they load the entire project graph, resolve generics variance, track nullability through call chains, and enforce stricter rules like phpstan-strict-rules or Psalm's taint analysis. They catch classes of bugs the IDE engine cannot see without full-project context — for example, a key that may be absent in an array returned from a repository method that conditionally returns null.
Since PhpStorm 2022.2, you can run PHPStan or Psalm as external inspections (Settings → Editor → Inspections → PHP → Quality Tools). Their results appear inline in the editor, merged with built-in warnings. This gives you CLI-grade errors without leaving the IDE — but only if the tools are installed in the project's vendor/ or a configured Docker/WSL interpreter.
A Pragmatic Split: Fast Feedback vs Deep Guarantees
Enable PhpStorm's built-in inspections for day-to-day coding. Keep the default profile, add your framework plugin (Laravel, Symfony, Drupal), and let the IDE catch typos, missing imports, and obvious type mismatches as you type. This costs almost zero latency.
Run PHPStan or Psalm at level 5+ in pre-commit hooks and CI. Use them for architectural rules: generics variance, dead-code detection across files, baseline suppression management, and the stricter null-safety that requires whole-program knowledge. Don't run them on every keystroke — that's what the IDE is for.
For ad-hoc whole-project scans, use PhpStorm's Run PHPStan / Run Psalm action (Ctrl+Shift+A). It invokes the CLI tool and streams results into the Problems tool window, where PhpStorm 2024.2+ merges duplicate messages from both engines.
Worked Example: Laravel Service Method
In a Laravel 11 project, a service method returns an array:
class UserService {
public function getProfile(int $id): array {
$user = $this->userRepository->find($id); // returns User|null
return ['user' => $user];
}
}
PhpStorm flags: Missing #[ArrayShape] attribute on return type. You add it:
#[ArrayShape(['user' => User::class])]
public function getProfile(int $id): array { ... }
PhpStorm is happy. But PHPStan level 6 reports: Array key 'user' may be absent because $user is User|null. The fix requires handling the null case:
#[ArrayShape(['user' => User::class])]
public function getProfile(int $id): array {
$user = $this->userRepository->find($id);
if ($user === null) {
throw new UserNotFoundException($id);
}
return ['user' => $user];
}
Now both tools pass. The IDE caught the missing shape; PHPStan caught the nullability flow. Neither alone would have produced the correct fix.
Trade-Off: CPU and UI Lag on Large Codebases
Running PHPStan or Psalm inside PhpStorm as external inspections consumes significant CPU and RAM. On a 50k-file codebase, automatic runs on every code change can lag the UI — typing feels sluggish, inspections queue up, the Problems window flickers.
Mitigation strategies:
- Disable automatic external inspection runs: Settings → Tools → PHPStan (or Psalm) → uncheck Run on code change. Trigger manually via Ctrl+Shift+A → "Run PHPStan".
- Offload to a remote backend: use PhpStorm's Remote Development (SSH, Docker, WSL2). The CLI tools run on the remote host; the local UI stays responsive. Ensure
composer require --dev phpstan/phpstanis installed in the remote environment and path mappings are correct. - If you use Xdebug profiling, don't run PHPStan simultaneously on a modest laptop — they compete for CPU.
Actionable Checklist
- Open Settings → Editor → Inspections → PHP → Quality Tools → PHPStan. Confirm the CLI path resolves (
vendor/bin/phpstanordocker-compose exec php phpstan). - Create a test file with an intentional error:
function foo(): int { return 'bar'; }. Verify PhpStorm underlines it instantly. Runvendor/bin/phpstan analysein terminal — it should report the same error at level 1+. - In a Laravel/Symfony project, enable the framework plugin (Settings → PHP → Frameworks). Check that route/controller inspections appear (e.g., missing route parameter).
- Trigger "Run PHPStan" from Ctrl+Shift+A. Observe the Problems tool window merging results. Try the "Suppress for statement" quick-fix (Alt+Enter) and confirm a
// @phpstan-ignore-next-linecomment is inserted. - If using Remote Development, verify the remote interpreter has PHPStan installed and PhpStorm's Deploy/Docker path mappings sync correctly.
Treat the two layers as complementary: the IDE for speed and syntactic correctness, the CLI for depth and architectural safety. A clean IDE pass doesn't guarantee PHPStan level 8 — and it shouldn't need to.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.