DEV Community

Cover image for PHP: Why I Use Static Analysis
Lars Moelleken
Lars Moelleken

Posted on

PHP: Why I Use Static Analysis

Introduction: Errors Belong to Developers, Not Users

#blogPostAsWebApp: https://voku.github.io/WhyPHPStan/

Let’s start with something simple and uncomfortable:

If an error happens at runtime, a user sees it.

I don’t care how good your monitoring is, how fast your rollback pipeline runs, or how often you say “it shouldn’t happen”. Runtime errors are already too late. They are failures that escaped development and reached production.

That’s the entire motivation for static analysis.

Not theory. Not fashion. Damage control.

PHP enforces reality.

Static analysis enforces possibility.

Two different layers. Two different failure modes.

And if we can use both, the real question is: why wouldn’t we?


PHP Enforces Reality. Static Analysis Enforces Possibility.

PHP is excellent at telling you when something actually breaks:

  • calling a method that doesn’t exist
  • passing a string where an int is required
  • accessing a property on null

That’s runtime reality.

But PHP is deliberately permissive before that point. It allows code to exist even if it can only fail under certain conditions, in certain branches, or after a refactor.

That’s where static analysis comes in.

Tools like PHPStan don’t compete with PHP. They operate earlier:

  • when code is written
  • when it’s refactored
  • when it’s reviewed
  • when it’s understood by someone new

PHP tells you what happened.

Static analysis tells you what should never have been possible.

Different jobs. Same goal.


High PHPStan Levels Don’t Just Find Bugs — They Shape Architecture

This is where many people misunderstand strict static analysis.

A high PHPStan level is not about “more errors”.

It’s an architectural pressure system.

It pushes you to:

  • stop passing anonymous arrays around
  • stop encoding meaning in strings
  • stop hiding invariants in comments
  • introduce value objects, enums, interfaces
  • define clear boundaries and contracts

And yes, there’s a very pleasant side effect:

👉 IDE autocompletion becomes frighteningly good

Because once types are precise, the IDE actually understands your system.

Not guesses. Not mixed. Not “trust me”.

That’s not cosmetic DX. That’s speed and confidence.


“Let PHP Enforce Contracts” — Good. Now Add the Missing Layer.

“Let PHP enforce contracts.”

Perfect. We should.

But nothing stops you from:

  • enforcing contracts at runtime and
  • enforcing them at design time

In fact, PHPStan often nudges you toward better PHP:

  • stricter parameter types
  • explicit return types
  • real interfaces instead of conventions
  • enums instead of magic strings

Using static analysis doesn’t weaken PHP’s guarantees.

It narrows the space in which PHP ever has to complain.


DX Matters: Instant Feedback Beats Any Test Suite

One of the most underrated benefits is developer experience.

If a function declares:

/**
 * @param int<1, 32> $limit
 */
function paginate(int $limit): void {}
Enter fullscreen mode Exit fullscreen mode

And I pass 64, I want that error:

  • immediately
  • in my IDE
  • without running tests
  • without pushing code
  • without involving CI

That feedback loop is priceless.

Static analysis is the only tool that gives you semantic errors at typing speed.


Security: literal-string Is a Real Guardrail

This is not theoretical.

/**
 * @param literal-string $sql
 */
function runRaw(string $sql): void {}
Enter fullscreen mode Exit fullscreen mode

This single constraint:

  • blocks user input
  • blocks “temporary” hacks
  • blocks accidental string concatenation
  • blocks whole classes of SQL injection mistakes

Does it solve all security? Of course not.

Does it eliminate dumb, expensive errors early? Absolutely.


Documentation That Cannot Lie

Traditional documentation rots because nothing enforces it.

Static PHPDoc does.

When you write:

/**
 * @return Generator<int, User>
 */
function users(): Generator {}
Enter fullscreen mode Exit fullscreen mode

You’ve documented:

  • key type
  • value type
  • iteration semantics

And if reality diverges? The build fails.

As a bonus:

  • the IDE knows $key is int
  • the IDE knows $user is User
  • refactors are safe
  • onboarding is faster

This is documentation with teeth.


Logic Bugs Still Exist 😊 — And That’s Fine

Let’s get this out of the way:

Static analysis does not prevent logic bugs.

Nothing does — except thinking.

But static analysis removes:

  • type bugs
  • shape bugs
  • nullability bugs
  • refactor bugs
  • “this can never happen” bugs

Those are not interesting problems.

They’re noise.

I prefer to spend my brainpower on logic and design, not on mistakes a machine can detect instantly.


The Practical Rule Set That Actually Works

This is how static analysis succeeds in real PHP codebases:

  • keep arrays and strings at the boundaries (HTTP, JSON, DB)
  • validate once
  • convert once
  • after that: objects, enums, value objects
  • baseline legacy code
  • tighten rules only where you touch code
  • never weaken types just to silence the tool

No rewrites. No greenfield fantasies.

Just steady entropy reduction.


Conclusion: Two Layers Are Better Than One

PHP enforces reality.

Static analysis enforces possibility.

Runtime errors mean errors for users.

Static errors mean errors for developers.

High PHPStan levels shape architecture for the better and massively improve IDE feedback.

Nothing prevents you from using strong PHP types and static analysis — in fact, they reinforce each other.

Logic bugs still require thinking 😊

But type bugs are optional.

And I prefer optional bugs to be… not present.


Summary

  • Runtime errors are user-facing failures
  • Static analysis shifts errors to development time
  • High PHPStan levels improve architecture and DX
  • Security and documentation benefit immediately
  • PHP and PHPStan are complementary, not competing
  • Logic bugs remain — but the boring ones disappear

What’s Next?

  • Using PHPStan as an architectural guardrail
  • Static analysis as a safety net for LLM-generated code (with a fast feedback loop)
  • Killing mixed once and for all

Happy Coding.

Top comments (0)