Většina vývoje v praxi probíhá na existujících systémech. Enterprise aplikace i úspěšné produkty běží roky a jejich codebase nese stopu tehdejších technologií i tlaku na rychlé dodávky. Řada z nich vznikla ještě v době PHP 5.x, kdy chyběl striktní typový systém a architektura se často přizpůsobovala potřebě rychle dodat MVP.

S příchodem PHP 8.x se rozdíl mezi starým a současným způsobem vývoje výrazně prohloubil. Moderní PHP nabízí robustní typovou kontrolu, immutabilitynativní Enums. Legacy aplikace přitom zůstávají zdrojem vysoké kognitivní zátěže. což znamená, že vývojáři tráví mnoho času reverzní analýzou kódu místo rozvoje funkcionality.

Typickou reakcí bývá úvaha o kompletním přepisu aplikace (The Big Rewrite). Z pohledu softwarové ekonomiky však jde o velmi rizikový krok. Udržitelnější cesta vede přes postupný refaktoring. Průběžná modernizace umožňuje využít možnosti PHP 8, stabilizovat architekturu a zároveň zachovat provoz i kontinuitu vývoje.

1. Analýza projektu a codebase

Legacy aplikace mají vrstevnatou strukturu. Každá část vznikala v jiném období, pod jinými požadavky a s jinou mírou disciplíny. Kvalita tak nikdy není rovnoměrná. Efektivní refaktoring proto vychází z pochopení toho, kde kód skutečně brzdí vývoj a kde je technický dluh pouze historický, ale relativně stabilní.

V praxi se jako nejspolehlivější ukazuje kombinace dvou metrik: Cyclomatic ComplexityCode Churn. Komplexita popisuje logickou náročnost, churn pak frekvenci změn. Jejich spojení dokáže odhalit oblasti, kde se riziko reálně koncentruje.

  • Modul, který je starý, nemá typy ani testy, ale několik let se do něj nesahalo, představuje nízké provozní riziko. Refaktoring takového místa má nízkou návratnost.
  • Soubory s vysokou komplexitou a opakovanými úpravami během sprintů tvoří jádro problémů. Každá změna zvyšuje pravděpodobnost regresí a stojí více času na analýzu.

Mezi nejčastější anti-patterny patří:

  • Implicitní datové struktury – předávání dat přes asociativní pole bez kontraktu; častý zdroj nejasností a chyb.
  • God Classes – třídy s příliš velkou odpovědností, které komplikují změny i testování.
  • Tight Coupling – statická volání a skryté závislosti blokují izolované testy a zpomalují refaktoring.
  • Helper funkce – odkladiště nesourodé logiky bez jasných hranic; vznikají duplicity a nečitelné flow.
  • Temporal Coupling – kód funguje správně jen při přesném pořadí volání; vysoká kognitivní zátěž.
  • Side-effect funkce – metody mění stav systému mimo návratovou hodnotu, což ztěžuje predikovatelnost i testování. Model – objekty mají jen data, logika je v servicích. Nízká soudržnost a pomalý vývoj změn.

2. Stabilizace chování: Characterization Tests

Refaktoring bez testů je vždy risk a náročný proces. Z mé zkušenosti je situace u legacy projektů ještě náročnější, protože často chybí testy, které mají skutečnou hodnotu. Ty, které existují, bývají pevně navázané na konkrétní implementaci, testují privátní metody, vyžadují rozsáhlé mockování a při změně struktury kódu selhávají, i když výsledné chování systému zůstává správné.

U legacy systémů proto nemá smysl snažit se dopsat Unit testy na všechno. Mnohem efektivnější je zavést Characterization Tests, tedy Golden Master přístup, který zafixuje chování systému zvenčí. Vybereme reprezentativní vstup, například HTTP request nebo importní XML, necháme ho projít aktuální implementací a uložíme výstup jako snapshot. Ten pak slouží jako externí kontrakt a okamžitě upozorní na změnu chování během refaktoringu. 

3. Aplikace typového systému PHP 8

Moderní PHP dokáže přesunout velkou část kontroly ze samotného běhu aplikace do statické analýzy. U legacy systémů jde o zásadní posun, protože velká část chyb nevzniká v byznysové logice, ale v nejasné struktuře dat a v implicitních předpokladech mezi jednotlivými vrstvami aplikace.

Přechod od polí k DTO (Data Transfer Objects)

Nejčastějším zdrojem chyb v dynamickém PHP je nejistota kolem struktury dat. Asociativní pole působí na první pohled jednoduše, ale při práci s nimi se vývojář nevyhne otázkám typu: Co v tom poli vlastně je? Jaké klíče mám čekat? Jaký typ tam přijde tentokrát? A je tahle hodnota vůbec povinná?

Takové pochybnosti zpomalují práci a chyby se často objeví až v runtime, kdy je jejich dohledávání o to náročnější.

Modernizace proto stojí na nahrazení polí typovanými objekty (DTO). PHP 8.2 nabízí readonly třídy i Constructor Property Promotion, takže jasně definovaná datová struktura nevyžaduje skoro žádný boilerplate. Výsledkem je přehlednější kód, menší prostor pro chyby a mnohem spolehlivější statická analýza.

class UserData {
  public function __construct(
    public string $email,
    public ?int $age = null,
    public UserStatus $status,
  ) {}
}

Doménové typy a Enums

Legacy kód bývá plný „magických hodnot“ (stringy jako 'active', 'deleted' nebo inty 1, 2). Když vývojář narazí na podobný řetězec, často si musí domýšlet, co přesně znamená a kde se ještě používá. To zvyšuje chybovost i kognitivní zátěž.

PHP 8.1 přineslo Backed Enums, které tenhle problém řeší mnohem čistěji. Hodnoty se přesunou do jednoho místa, dostanou jasný význam a mohou mít i vlastní metody.

Kód je díky tomu čitelnější, soudržnější a změny v doméně se dělají na jednom místě místo lovení magických hodnot napříč projektem.

enum UserStatus: string {
    case Active = 'active';
    case Blocked = 'blocked';

    public function isEditable(): bool {
        return $this === self::Active;
    }
}

Strict Types a Nullability

declare(strict_types=1); eliminuje implicitní přetypování, které je v dynamickém PHP častým zdrojem nejasných chyb. Ještě větší přínos však přináší důsledná práce s hodnotami, které mohou být null. Legacy kód často spoléhá na tichý předpoklad, že proměnná null neobsahuje, a právě tato situace pak vede k obtížně reprodukovatelným chybám.

Použití nullable typů, jako je ?string nebo ?int, nutí programátory s těmito variantami pracovat vědomě. Možné stavy proměnných jsou popsány přímo v rozhraní metod a objektů, což výrazně zvyšuje předvídatelnost aplikace. Výsledkem je stabilnější chování systému, méně skrytých selhání a lepší podpora statické analýzy.

Moderní PHP kód by měl být plně typovaný – od parametrů a návratových hodnot až po interní proměnné – protože typové deklarace zpřesňují kontrakty mezi vrstvami, zvyšují předvídatelnost chování a umožňují statické analýze odhalit chyby včas.

4. Automatizace a statická analýza

U rozsáhlých legacy aplikací je ruční hlídání kvality kódu nereálné. Code review dokáže zachytit architektonické problémy, ale není vhodné pro systematickou kontrolu typů, chybějících návratových hodnot nebo nedefinovaných struktur. Tyto úkoly musí převzít automatizace.

Statická analýza a CI pipeline společně tvoří kontrolní vrstvu, která odfiltruje většinu chyb ještě před tím, než se dostanou do produkce.

PHPStan a baseline jako nástroj postupné stabilizace

PHPStan (nebo Psalm) je pro moderní PHP ekosystém standard. U legacy projektů však nastává typická situace: při prvním spuštění nástroj nahlásí stovky až tisíce chyb. Opravit je najednou není reálné.

Praktické řešení představuje baseline. Uloží aktuální stav chyb, CI ho ignoruje, ale nový kód musí být v pořádku. Tím se zastaví další degradace projektu a zároveň se vytvoří prostor pro postupné čištění.

Doporučený postup:

  1. Nastavit PHPStan na úroveň, která poskytuje praktické výsledky (například level 5).
  2. Vygenerovat baseline.
  3. Zajistit, aby nový kód neobsahoval chyby, které baseline nezná.
  4. Postupně odstraňovat chyby z baseline v rámci běžného vývoje.

Tím se zavede přísnější kontrola bez toho, aby se tým dostal do paralýzy způsobené příliš velkým množstvím problémů najednou.

Automatizovaný refaktoring pomocí Rectoru

Rector pracuje nad abstraktním syntaxovým stromem a umožňuje měnit strukturu kódu mechanicky. Hodí se zejména v okamžicích, kdy je potřeba provést zásah ve velkém počtu souborů.

Mezi běžné úlohy patří:

  • doplnění typových deklarací na základě PHPDoc,
  • migrace syntaktických konstrukcí na moderní ekvivalenty,
  • odstranění mrtvého kódu,
  • konsolidace opakujících se fragmentů.

Rector dokáže ušetřit velké množství času a eliminuje chyby způsobené manuálními úpravami. Každý výstup však musí být podložen testy. Nástroj není náhradou za doménovou znalost a jeho úpravy mohou narazit na speciální případy, které analyzátor nerozpozná.

Standardizace kódu pomocí CS-Fixer nebo Laravel Pint

Konzistentní styl kódu má přímý dopad na rychlost práce. Diffy jsou kratší, konflikty při mergích méně časté a kontext se čte rychleji. To je obzvlášť důležité u legacy projektů, kde se střídalo mnoho autorů.

Nástroje jako PHP-CS-Fixer nebo Pint automatizují formátování a udržují jednotný styl. Díky tomu se vývojáři mohou soustředit na podstatné části review místo řešení mezery, závorky nebo konvencí.

CI pipeline jako předpoklad bezpečného refaktoringu

Automatizace bez CI pipeline ztrácí smysl. Každý pull request musí projít sadou kontrol, které zahrnují:

  • statickou analýzu,
  • spouštění testů,
  • kontrolu stylu kódu,
  • případně analýzu závislostí a bezpečnostních rizik.

Teprve v takovém prostředí je možné přistoupit ke strukturálním změnám bez obavy, že se chyby projeví až po nasazení.

5. Strukturální refaktoring: Decoupling

Jakmile jsou typy stabilní a aplikace je pokrytá testy, je možné posunout refaktoring na úroveň architektury. Cílem této fáze není změnit strukturu podle „dokonalého návrhu“, ale odstranit provázanost, zpřehlednit toky dat a zpřístupnit logiku v menších, lépe uchopitelných komponentech.

Strukturální refaktoring je investice, která zlepšuje čitelnost, testovatelnost i odolnost systému při změnách.

Action Classes namísto Services

V legacy aplikacích se často setkáváme s „Service“ třídami, které se staly odkladištěm nesouvisejících metod. Alternativním přístupem je pattern Action Classes (nebo Single Action Controllers). Každá třída zapouzdřuje právě jeden byznysový případ (Use Case), např. ApproveOrderAction. Taková třída má jasně definované závislosti, jednu veřejnou metodu a je triviálně testovatelná.

Action Classes jako alternativa k rozsáhlým Services

V legacy projektech bývá pojem „Service“ často příliš široký. Do jedné třídy se dostávají nesouvisející funkce, které obsluhují různé části domény. Testování i změny jsou pak náročné, protože jedna úprava ovlivní celou třídu.

Praktičtější přístup představují Action Classes. Každá třída zapouzdřuje jeden konkrétní use case, například ApproveOrderAction nebo GenerateInvoiceAction. Výsledkem je:

  • jasně definovaný vstup a výstup,
  • explicitní závislosti,
  • přímá testovatelnost bez nutnosti připravovat rozsáhlé mocky.

Action Classes snižují kognitivní zátěž tím, že omezují kontext, se kterým musí vývojář pracovat. Zároveň přirozeně podporují oddělení doménové logiky od infrastruktury.

Minimalizace skrytých závislostí pomocí Dependency Injection

Skryté závislosti patří mezi nejčastější překážky při refaktoringu. Třída, která sama instancuje své závislosti nebo používá statická volání, znemožňuje izolované testování a často zakrývá skutečný rozsah své odpovědnosti.

Explicitní Dependency Injection řeší oba problémy:

  • závislosti jsou přehledně deklarované v konstruktoru,
  • každá třída přijímá pouze to, co skutečně potřebuje,
  • velký počet závislostí automaticky odhaluje místa, která je vhodné rozdělit.

Tento způsob řízení závislostí vede k přirozenému zpřehlednění architektury. Vývojář nemusí uvažovat o tom, „co si třída někde uvnitř vytvoří“, protože vše je viditelné přímo z jejího rozhraní.

Rozdělení monolitických tříd na menší komponenty

V legacy kódu se často nacházejí třídy, které převzaly více rolí, než je dlouhodobě udržitelné. Porušení principu Single Responsibility se v praxi projevuje tím, že třída kombinuje validace, doménová rozhodnutí i práci s infrastrukturou. Změna jedné části pak ovlivní zbytek a tým tráví více času hledáním vedlejších efektů než samotným vývojem.

Refaktoring takových tříd je postupný a navazuje na testy i typový systém:

  1. Oddělit oblasti, které mají vlastní odpovědnost.
  2. Vytvořit odpovídající komponenty, například Value Objects, Actions nebo menší doménové služby.
  3. Přesouvat logiku po částech a po každém kroku ověřit chování testy.

Tento postup výrazně snižuje riziko regresí. Místo jednoho velkého přepisu vzniká série malých změn, které jsou kontrolovatelné a zpětně dohledatelné.

6. Procesní stránka: Kontinuální zlepšování

Technický dluh nelze splatit jednorázově. Je nutné změnit mindset týmu. Osvědčenou praxí je Boy Scout Rule (pravidlo skautů): „Nech kód o něco čistší, než jsi ho našel.“

V praxi to znamená:

  1. doplnit typy v metodě, která se právě upravuje,
  2. zpřesnit názvy proměnných, pokud neodpovídají skutečnému významu,
  3. rozdělit příliš dlouhou metodu na několik menších,
  4. odstranit zbytečné podmínky nebo duplicity.

Proto není dobré bárt refaktoring jako samostatnou fázi. Patří přímo do procesu vývoje funkcionality. Tým má přehled o konkrétní části systému, kterou rozšiřuje, a právě v tom okamžiku je nejefektivnější provést i menší architektonické úpravy.

Závěr

Modernizace legacy aplikace není jednorázový úkol. Je to proces, který kombinuje technické kroky s dobrou organizací práce. Typový systém PHP 8, statická analýza, automatizace a postupný refaktoring vytvářejí prostředí, ve kterém je možné vylepšovat architekturu bez ohledu na stáří kódu.

Klíčovým principem je postupovat po malých, bezpečných krocích. Stabilizovat chování pomocí testů, zavést předvídatelné datové kontrakty, zpřesnit hranice systému a odstranit skryté závislosti. Tyto úpravy přinášejí okamžitý přínos a zároveň připravují aplikaci na další rozvoj.

Důležitá je i každodenní práce týmu. Pokud se zlepšování stane součástí běžného vývoje, technický dluh přestane být bariérou a projekt se stane předvídatelnějším i udržitelnějším. Takový přístup neřeší problémy jen krátkodobě. Umožňuje posunout systém do stavu, ve kterém se dá dlouhodobě stavět, rozšiřovat a bezpečně inovovat.