P
imvdmolen.nl
Blog

Refactoren van legacy code met behulp van Laravel's built-in functionaliteiten

Onlangs kwam ik een Laravel-project tegen dat al vijf jaar oud was. De code functioneerde nog steeds, maar elke kleine aanpassing kostte me uren omdat alles door elkaar heen zat. Controllers van 500 regels, queries direct in de views, en overal hardcoded waarden. Precies het soort legacy code waar elke developer wel eens tegenaan loopt. Het refactoren van zulke code is een kunst op zich, en Laravel biedt gelukkig veel tools om dit proces behapbaar te maken.

Legacy code herken je meestal aan een paar duidelijke signalen. Methoden die veel te veel verantwoordelijkheden hebben, directe database-calls in controllers, ontbrekende validatie, en code die niemand meer durft aan te raken uit angst dat alles kapot gaat. Tijdens mijn dagelijkse werk zie ik vaak dat developers dit soort code vermijden, maar juist hier liggen de grootste kansen voor verbetering. Een goed refactorplan begint altijd met het identificeren van de grootste pijnpunten, niet met het proberen alles tegelijk op te lossen.

Voorbereiding van een effectief refactorplan

Analyse van bestaande code vereist geduld en een systematische aanpak. Ik begin altijd met het doorlopen van de routes om te begrijpen welke functionaliteit de applicatie heeft. Vervolgens kijk ik naar de controllers om te zien waar de grootste knelpunten zitten. Vaak vind je daar controllers die meerdere modellen aanspreken, complexe business logic bevatten, en direct queries uitvoeren.

Database-structuur verdient speciale aandacht omdat veel legacy problemen daar hun oorsprong vinden. Tabellen zonder foreign keys, ontbrekende indexen, en kolommen vol met JSON-data die eigenlijk aparte tabellen hadden moeten zijn. Laravel's migrations maken het gelukkig mogelijk om deze structurele problemen stap voor stap op te lossen zonder de hele database overhoop te gooien.

Tests schrijven voor bestaande functionaliteit is cruciaal voordat je begint met refactoren. Zonder tests weet je nooit zeker of je iets kapot hebt gemaakt. PHPUnit integration tests zijn hiervoor ideaal omdat ze de hele flow van request tot response controleren. Feature tests geven me het vertrouwen dat de applicatie na elke refactorstap nog steeds werkt zoals verwacht.

Het gebruik van Laravels built-in refactoringshulpmiddelen

Dependency injection is waarschijnlijk de krachtigste tool in Laravel voor het opruimen van legacy code. Controllers die direct models instantiëren zijn een veelvoorkomend probleem dat je elegant oplost met constructor injection. Door services via de constructor te injecteren, maak je code testbaar en modulair.

use App\Services\UserService;

class UserController extends Controller
{
    private $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    public function index()
    {
        $users = $this->userService->getAllUsers();
        return view('users.index', compact('users'));
    }
}

Service providers helpen bij het centraliseren van complex object creation. Legacy code bevat vaak heel veel new statements verspreid door de applicatie. Door services te registreren in providers, creëer je een centrale plek waar je dependencies configureert. Dit maakt het ook makkelijker om later implementations te wisselen zonder de hele codebase te moeten doorzoeken.

Eloquent relationships zijn een andere gamechanger voor legacy code. Raw queries en manual joins vervang je stap voor stap door elegante relationship definitions. Dit vermindert niet alleen de hoeveelheid code, maar maakt het ook veel minder foutgevoelig. Eager loading oplost meteen die vervelende N+1 query problemen die legacy code vaak heeft.

Geavanceerde refactoringsTechnieken met Laravel

Repository patterns introduceer ik altijd als de business logic te complex wordt voor Eloquent models. Legacy code heeft vaak controllers die rechtstreeks met meerdere models praten en ingewikkelde queries uitvoeren. Door repositories te maken, isoleer je deze data access logic en maak je het testbaar.

Service classes zijn perfect voor business logic die niet thuishoort in models of controllers. Denk aan het versturen van emails, het verwerken van betalingen, of het synchroniseren van externe APIs. Legacy code stopt dit soort logica vaak direct in controllers, wat testing en hergebruik lastig maakt.

class OrderProcessingService
{
    public function processOrder(Order $order): bool
    {
        $this->validateOrder($order);
        $this->processPayment($order);
        $this->sendConfirmationEmail($order);
        $this->updateInventory($order);
        
        return true;
    }
}

Events and listeners zijn fantastisch voor het ontkoppelen van code die te strak aan elkaar zit. Legacy applicaties hebben vaak controllers die tientallen dingen doen nadat een actie is voltooid. Door events te dispatchen, maak je de code modulair en uitbreidbaar. Nieuwe functionaliteit voeg je toe door simpelweg een nieuwe listener te registreren.

Middleware gebruik ik voor cross-cutting concerns zoals logging, caching, en authorization. Legacy code dupliceert dit soort logica vaak in verschillende controllers. Custom middleware centraliseert deze functionaliteit en maakt controllers schoner en focusser.

Implementatie en testen van gerefactoreerde code

Unit tests schrijf ik voor elke nieuwe service class en repository die ik introduceer tijdens refactoring. Mock objects maken het mogelijk om components geïsoleerd te testen zonder afhankelijkheden zoals databases of externe APIs. PHPUnit's mocking capabilities zijn hiervoor uitstekend geschikt.

Feature tests controleren dat de applicatie als geheel nog steeds werkt na refactoring. Deze tests maken HTTP requests naar je applicatie en controleren responses, net zoals een echte gebruiker zou doen. Ze geven me het vertrouwen dat ik geen functionality heb gebroken tijdens het opschonen van code.

Static analysis tools zoals PHPStan en Psalm vind ik onmisbaar voor legacy code refactoring. Ze vinden type errors, undefined variables, en andere problemen die anders pas runtime zichtbaar worden. Psalm's security analysis helpt ook bij het identificeren van potentiële vulnerabilities in oude code.

composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse app/ --level=5

Deployment automation wordt cruciaal wanneer je veel kleine refactoring changes maakt. Laravel Envoy scripts automatiseren het deployment proces en zorgen voor consistency tussen environments. Zero-downtime deployments met tools zoals Laravel Forge maken het mogelijk om frequent kleine updates te deployen zonder gebruikers te hinderen.

Code reviews krijgen extra aandacht tijdens refactoring projects. Elke pull request documenteer ik uitgebreid met uitleg over wat er is veranderd en waarom. Screenshots van voor en na helpen reviewers te begrijpen wat de impact is. Automated tests in CI pipelines controleren dat alles nog werkt voordat code merged wordt.

Refactoring legacy Laravel code is een marathon, geen sprint. Ik heb geleerd dat kleine, incrementele veranderingen veel effectiever zijn dan grote rewrites. Door Laravel's tools slim te gebruiken en altijd tests te schrijven, transformeer je stap voor stap onhoudbare legacy code naar moderne, maintainable applicaties. Het geeft enorme voldoening om een oude codebase nieuw leven in te blazen.