009 —AI
AI-codereview op legacy PHP: vangsten en blinde vlekken
Een eerlijke kijk op wat AI-codereview echt verandert bij het refactoren van 14 jaar oude PHP: de bugs die je in seconden vindt en de keuzes die het mist.
Het is woensdag, half vier. Je bent drie uur bezig met een refactor van een 14 jaar oud WordPress-thema dat een Nederlands bureau waar we mee werken heeft overgenomen van een lang vertrokken freelancer. Het bestand is functions.php. Regel 312 luidt extract($_REQUEST);. De AI-reviewpass markeerde het binnen een seconde, samen met veertien andere bevindingen verspreid over wp-content/themes/. Geen ervan is fout. Ongeveer de helft doet ertoe.
Dit is de perspectiefverschuiving die niemand echt heeft opgeschreven: AI-ondersteunde codereview op legacy PHP maakt het werk niet op de voor de hand liggende manier sneller. Het verschuift de bottleneck. Het detectieprobleem is grotendeels opgelost. Het beslisprobleem is nu het hele werk.
Wat het model in seconden ziet
Voor bugs op patroon-niveau zijn de huidige modellen heel goed. Geef ze een legacy codebase van 600 bestanden in WordPress of Magento 1 en een AI-pass haalt er betrouwbaar het volgende uit:
- Elke overgebleven
mysql_query()-aanroep, deprecated in PHP 5.5 en verdwenen in PHP 7. - Niet-prepared SQL met geconcateneerde
$_GET- of$_POST-waarden. - Aanroepen van
create_function(),each(),split()en de rest van de PHP 7.2 deprecatielijst. - Type-juggling bugs rond
==op stringachtige getallen (zie de vergelijkingstabel op php.net). - SSRF-achtige
file_get_contents()-aanroepen op door de gebruiker bepaalde URL's. - Output die naar de pagina geschreven wordt zonder
esc_html()ofesc_attr().
Een senior PHP-ontwikkelaar die deze audit met de hand doet, is een week per site bezig. Een model dat op voldoende PHP getraind is doet het in de tijd die nodig is om koffie te zetten. De output is ongeveer zo accuraat als een vermoeide mens, mogelijk iets beter op de saaie patroonmatches, zeker minder goed op alles waarvoor je de historie van het project moet lezen.
Dit deel van de verschuiving is echt en onomstreden. Was een audit in 2024 nog 'zoek alle kapotte stukken', dan begint een audit in 2026 met een printbare lijst kapotte stukken en de vraag 'welke daarvan pakken we aan'.
Waar het model met overtuiging mis is
De fouten zitten bij context, niet bij vermogen. Een paar die we steeds tegenkomen op echte legacy sites.
De ontbrekende break die geen bug is
Een switch-case zonder break ziet eruit als een duidelijke omissie. Op een Drupal 7 site die we hebben gecontroleerd was de ontbrekende break dragend: hij viel met opzet door naar een legacy redirect die een analytics-consultant in 2017 had aangelegd. Tweeduizend pagina's per maand raken die fall-through. Het model zei 'waarschijnlijk bug'. Het was in feite de business-logica.
Dode code die nog steeds wordt aangeroepen
Legacy PHP houdt van require_once met relatieve paden twee directories omhoog. Een AI-rapport noemt een functie zelfverzekerd dood omdat niets in het bestand of de namespace ernaar verwijst. Vervolgens includeert een Joomla-module uit 2012 dat bestand rechtstreeks en klapt de productie de ochtend na de deploy.
De deprecated functie die de host heeft vastgepind
Sommige shared hosts serveren nog steeds PHP 7.4, soms 7.2. Het model raadt aan om each() of create_function() te vervangen door het moderne equivalent, wat op zichzelf klopt en stukgaat zodra je pusht naar een host die drie major versies achterloopt. De fix vereist dat je de .htaccess leest:
AddHandler application/x-httpd-php74 .php
# of, op cPanel:
# AddType application/x-httpd-ea-php74 .php .php7 .phtml
Die regel ziet het model niet, tenzij je hem aanlevert. Het geeft het juiste antwoord voor de verkeerde PHP-versie en noemt het af.
Het patroon van een twee keer geregistreerde hook is een stillere neef van hetzelfde probleem. WordPress-plugins uit de jaren 2014 tot 2018 registreren regelmatig hetzelfde filter opnieuw binnen zowel plugins_loaded als init, omdat een concurrerende plugin het ooit deregistreerde. De bevinding 'duplicate hook registration' klopt technisch en is operationeel catastrofaal.
De reviewloop, opnieuw geschreven
De vorm van een legacy PHP-refactor was vroeger lineair. Lees het bestand. Schrijf notities in een kladblok. Patroon-match tegen bugs die je eerder hebt gezien. Beslis wat je verandert. Run de wijziging tegen een staging-kopie. Wacht tot een klant mailt over het ding dat je hebt gesloopt.
Met AI-review in de loop ziet de vorm er nu zo uit:
- Run het model over de hele tree.
- Triëer de bevindingen tegen de echte historie van de codebase.
- Schrijf voor elke bevinding die het waard is om op te pakken de wijziging op als een hypothese.
- Verifieer tegen een echte database en een echte requestflow.
- Houd een undo-pad open totdat de klant er een week mee heeft geleefd.
Stap 2 is het werk. Daar verdient domeinkennis zich terug. Een bevinding als 'de wp_users-query op regel 88 is niet prepared' is een hypothese, totdat je de git blame, de support tickets en het gedrag op staging hebt gecontroleerd. Het model heeft geen ongelijk. Het weet alleen niet of de variabele uit een constante in wp-config.php komt of uit een querystring die een klant zelf kan opstellen. De OWASP-uitleg over SQL injection is nog steeds de juiste primer voor die afweging.
Triage als de eigenlijke vaardigheid
De senior ontwikkelaars met wie we werken worden merkbaar beter in één specifieke beweging: een AI-bevinding lezen en in ongeveer vijftien seconden beslissen of die een echt onderzoek waard is. De beweging gaat zo:
- Is de input attacker-controlled? Zo ja, echt lezen.
- Loopt deze code-pad daadwerkelijk op de productiesite? Check de access logs, niet het bestand.
- Als we het veranderen, kunnen we het dan met één klik terugzetten?
Die derde vraag werd vroeger beantwoord met 'ik zip het bestand eerst even'. Nu wordt hij beantwoord door version history op bestandsniveau op het project zelf, wat de drempel om de wijziging te proberen wegneemt. De kosten van een verkeerde triage-inschatting dalen tot ongeveer nul, wat betekent dat de lat voor 'het proberen waard' ook zakt.
Wat dit verandert aan het werk
Van begin tot eind lijkt de dag minder op archeologie en meer op redactiewerk. Je begint met een lijst verdachte regels. Je beslist over vijf ervan. Je voert de wijziging door. Je houdt het audit trail intact. Je gaat door. Wat AI niet heeft gedaan, is de ontwikkelaar overbodig maken. Het schoof ze een laag omhoog, van 'vind de bug' naar 'beslis welke bug echt is'.
Toen we Pier bouwden liepen we tegen precies deze loop aan op elke legacy site die we aanraakten. Hoe we het uiteindelijk hebben opgelost was om de AI-review, de FTP-edit, de MySQL editor en de version history per bestand in één venster te zetten, zodat een triagebeslissing en het undo-pad één toetsaanslag van elkaar af zitten.
Het kleinste wat je vandaag kunt doen: pak de oudste functions.php in je projecttree, draai er een AI-reviewpass op, en lees voor je iets fixt de git blame op de drie meest alarmerende bevindingen. De helft daarvan is een echte bug. De andere helft leert je iets over de site dat het model niet kon zien.
— Vragen —
Vangt AI betrouwbaar SQL injection in legacy PHP?
Ja voor het voor de hand liggende string-concat patroon. Het mist de subtielere gevallen waarbij input door drie functieaanroepen en een sessievariabele heen gaat. Behandel elke flag als een hypothese om te verifiëren.
Vervangt AI-codereview senior PHP-ontwikkelaars?
Het vervangt het detectiedeel van hun werk. De triagebeslissing over wat veilig is om te veranderen blijft bij wie de historie van de codebase en de eigenaardigheden van de klant kent.
Hoe accuraat is AI op deprecatie-audits van PHP 7 naar 8?
Erg accuraat op patroonmatches als create_function of each. Minder betrouwbaar als de host op een oudere PHP-versie draait, want het model kan je .htaccess niet lezen tenzij je hem aanlevert.