Bugs vinden vóórdat je code ook maar één keer draait, dat is precies wat static analysis met PHPStan en Psalm mogelijk maakt. Deze tools lezen je PHP-code als een kritische collega: ze wijzen je op typefouten, onbereikbare branches, verkeerd gebruikte argumenten en null-pointer issues die je anders pas in productie zou ontdekken.
In moderne PHP-projecten zijn PHPStan en Psalm onmisbaar geworden. Samen met PHP 8.x features zoals readonly properties en enums til je de betrouwbaarheid van je codebase naar een niveau dat voorheen alleen statisch getypte talen haalden.
In dit artikel leer je hoe static analysis werkt, wat de verschillen zijn tussen PHPStan en Psalm, en hoe je ze praktisch inzet in je project.
Wat is static analysis?
Static analysis (statische code-analyse) is het proces waarbij een tool je broncode analyseert zonder die uit te voeren. De analyzer leest je bestanden, bouwt een model van types en control flow op, en controleert dat model op inconsistenties.
Het verschil met dynamische analyse is belangrijk. Unit tests, zoals uitgelegd in testing in PHP, draaien je code daadwerkelijk en controleren gedrag. Static analysis daarentegen kijkt alleen naar wat er op papier staat.
Waar static analysis in uitblinkt:
- Typefouten opsporen (verkeerde argumenten, return types)
- Null-dereferences detecteren
- Onbereikbare code vinden
- Ongebruikte variabelen en imports melden
- Verkeerd gebruik van API's signaleren
Je krijgt een soort "compiler-check" voor PHP, een taal die van oorsprong geen strikte compile-fase kent.
Waarom static analysis in PHP?
PHP is historisch gezien een losjes getypeerde taal. Variabelen kunnen van type veranderen, functies accepteren regelmatig mixed input en null sluipt makkelijk in onverwachte plekken. Dat maakt PHP flexibel, maar ook foutgevoelig.
Sinds PHP 7 en zeker met PHP 8.x is de typesituatie sterk verbeterd: union types, intersection types, readonly properties en enums geven je veel meer grip. Static analysis tools benutten deze metadata om diepgaande checks uit te voeren die de PHP-interpreter zelf niet doet.
Een paar veelvoorkomende bugs die een tool als PHPStan direct vangt:
- Een methode aanroepen op een object dat
nullkan zijn - Een array-sleutel gebruiken die niet gegarandeerd bestaat
- Een functie aanroepen met het verkeerde aantal argumenten
- Inconsistente return types tussen subclasses
Deze bugs kosten in productie vaak veel meer tijd dan de paar seconden die de analyzer nodig heeft.
PHPStan: de populairste keuze
PHPStan, ontwikkeld door Ondřej Mirtes, is inmiddels de meest gebruikte static analysis tool in de PHP-wereld. De tool is pragmatisch, snel en heeft een enorm ecosysteem aan extensies voor frameworks als Laravel, Symfony en Doctrine.
Installatie
Installeer PHPStan eenvoudig via Composer:
composer require --dev phpstan/phpstan
Maak een configuratiebestand phpstan.neon aan in de root van je project:
parameters:
level: 5
paths:
- src
- tests
excludePaths:
- vendor
Run vervolgens de analyse:
vendor/bin/phpstan analyse
Levels van 0 tot 10
Het krachtige aan PHPStan is het level-systeem. Je start op niveau 0 (basale checks) en werkt op richting niveau 10 (maximaal strikt). Elk niveau voegt extra regels toe:
- Level 0: basis syntax checks, undefined classes
- Level 2: unknown methods op objecten
- Level 5: argumenttypes worden gecontroleerd
- Level 8: nullable types moeten expliciet afgehandeld worden
- Level 10: strikste controle, inclusief
mixedtypes
Deze gelaagde aanpak maakt PHPStan geschikt voor zowel legacy projecten als greenfield codebases.
Voorbeeld van een melding
Stel je hebt deze functie:
function getUserName(?User $user): string
{
return $user->getName();
}
PHPStan meldt op level 8:
Cannot call method getName() on User|null.
Dat is precies het soort null-bug dat je in productie een 500-error oplevert. Fix het door expliciet te checken:
function getUserName(?User $user): string
{
return $user?->getName() ?? 'Anoniem';
}
Psalm: de strenge alternatief
Psalm, ontwikkeld door Vimeo, is de andere grote speler. Psalm voelt vaak strenger aan, met name rond nullability, immutability en type-narrowing. Ook heeft Psalm ingebouwde security-checks via "taint analysis" die bijvoorbeeld SQL injection of XSS risico's kan opsporen.
Installatie
composer require --dev vimeo/psalm
vendor/bin/psalm --init
Het --init commando scant je project en stelt automatisch een startlevel voor. Psalm gebruikt levels 1 tot 8, waarbij 1 het strikst is (omgekeerd van PHPStan).
Sterke punten van Psalm
- Taint analysis: detecteert onveilige data-paden (user input → SQL query)
- Immutability checks: markeer klassen als
@psalm-immutable - Advanced generics: uitgebreide ondersteuning voor template types
- Type aliases: definieer herbruikbare types in docblocks
Psalm is vooral waardevol in projecten waar security en strikte types belangrijk zijn, zoals API-platformen en financiële systemen. Bekijk ook validatie en security basics voor meer context over veilig programmeren.
PHPStan vs Psalm: welke kies je?
Beide tools doen fundamenteel hetzelfde werk. De keuze hangt af van je project en voorkeuren.
| Aspect | PHPStan | Psalm |
|---|---|---|
| Populariteit | Zeer hoog | Hoog |
| Ecosysteem | Groot, veel extensies | Kleiner, wel rijk |
| Taint analysis | Via extensies | Ingebouwd |
| Configuratie | NEON-formaat | XML |
| Leercurve | Gematigd | Steiler |
| Snelheid | Zeer snel | Snel |
Voor de meeste projecten is PHPStan de pragmatische eerste keus. Heb je security-kritische code of wil je maximale strictness? Dan loont Psalm.
Sommige teams gebruiken beide. Dat geeft de beste dekking maar verdubbelt onderhoud op configuratie en baselines.
Docblocks en generics
Beide tools halen veel informatie uit PHPDoc-annotaties. Ook als PHP zelf een type niet native ondersteunt, kun je het documenteren:
/**
* @param list<User> $users
* @return array<string, int>
*/
function countUsersByRole(array $users): array
{
// ...
}
list<User> betekent: een array met oplopende integer keys en alleen User objecten. array<string, int> is een associatieve array met string-keys en integer-values. Dit soort generics zijn essentieel voor betrouwbare analyse van collecties en arrays in PHP.
Integratie in je workflow
Static analysis werkt alleen als je het consequent draait. Integreer de tools op drie plekken:
1. Lokaal tijdens development
Voeg scripts toe aan composer.json:
"scripts": {
"analyse": "phpstan analyse --memory-limit=1G",
"psalm": "psalm --no-cache"
}
Zo run je de analyse met composer analyse voordat je commit.
2. In je CI-pipeline
Laat elke pull request automatisch geanalyseerd worden. GitHub Actions, GitLab CI en Bitbucket Pipelines ondersteunen dit probleemloos. Een PR die niet door de analyse komt, mergt niet.
3. Pre-commit hooks
Tools als Husky of captainhook/captainhook laten je de analyse draaien vóór elke commit. Kleinschalig maar effectief.
De baseline: static analysis toevoegen aan legacy code
Een bestaand project met duizenden regels code gaat niet zomaar door level 8 PHPStan. Beide tools ondersteunen een baseline: een bestand met alle huidige meldingen als "bekende issues".
Voor PHPStan:
vendor/bin/phpstan analyse --generate-baseline
Dit produceert phpstan-baseline.neon. Alle bestaande meldingen worden genegeerd, maar nieuwe code moet voldoen aan het ingestelde level. Zo voorkom je verdere degradatie en kun je stap voor stap de baseline verkleinen.
Dezelfde aanpak werkt voor Psalm via psalm --set-baseline=psalm-baseline.xml.
Static analysis en architectuur
Strikte typing dwingt je richting betere architectuur. Als je werkt met SOLID principes, design patterns of domain-driven design, dan versterkt static analysis die patronen.
Value objects worden vanzelf consistent, aggregates krijgen duidelijke interfaces, en services met afhankelijkheden worden door PHPStan of Psalm bewaakt op correct gebruik. Het is eigenlijk een gratis reviewer die nooit moe wordt.
Veelgemaakte fouten bij het invoeren
Let op deze valkuilen:
- Te hoog starten: level 8 op een legacy project geeft je duizenden meldingen. Start laag.
- Baseline als permanente oplossing zien: de baseline is een tijdelijke concessie, niet een einddoel.
- Alleen lokaal draaien: zonder CI-integratie sluipt er toch gebroken code in main.
- Docblocks verwaarlozen: zonder goede annotaties mist de tool context.
mixedaccepteren: probeer concrete types te gebruiken waar mogelijk.
Bekijk voor verdere verdieping ook de officiële PHPStan documentatie en de Psalm documentatie.
Veelgestelde vragen
Wat is static analysis in PHP?
Static analysis is een techniek waarbij je broncode analyseert zonder hem uit te voeren. Tools zoals PHPStan en Psalm detecteren typefouten, onbereikbare code en bugs voordat je code draait.
Wat is het verschil tussen PHPStan en Psalm?
Beide tools doen hetzelfde werk, maar Psalm is vaak strenger rond nullability en heeft sterkere security-checks. PHPStan is populairder, heeft meer extensies en een eenvoudiger configuratie.
Vervangt static analysis unit tests?
Nee, ze vullen elkaar aan. Static analysis vangt type- en logicafouten af, terwijl unit tests het daadwerkelijke gedrag van je code controleren. Je hebt beide nodig voor robuuste applicaties.
Op welk level moet ik starten met PHPStan?
Begin op level 0 of 1 en verhoog stapsgewijs. Zo voorkom je honderden meldingen tegelijk en kun je je codebase gecontroleerd opschonen naar het maximum van level 10.
Kan ik static analysis toevoegen aan een bestaand project?
Ja, beide tools ondersteunen een baseline-bestand. Je legt de huidige meldingen vast als bekend probleem en dwingt nieuwe code op een hoger niveau af, zonder direct alles te hoeven fixen.