Přejít k navigační liště

Zdroják » PHP » DressCode si vezme váš PHP kód do parády

DressCode si vezme váš PHP kód do parády

Články ‐ ‐ PHP ‐

Napsat nové pravidlo do PHP CS Fixeru nebo PHP_CodeSnifferu je za trest. David Grudl to zkusil kvůli jedinému řádku `use function` a místo pravidla z toho vznikl nový nástroj DressCode, postavený nad stromem, který ze zdrojáku nic nezahodí. Ukážeme si, jak ho nasadit, nastavit a zapojit do práce AI agenta.

Píšete před globální funkce zpětné lomítko? \strlen(), \in_array() nebo radši use function v hlavičce souboru? A má to vůbec nějaký smysl? Touhle otázkou otevřel David Grudl svou přednášku na Pražském srazu přátel PHP 30. září 2026.

Odpověď zní: ano, ale jen někdy. Zajímavější je ovšem cesta k ní. David chtěl do Nette Coding Standard doplnit jedno pravidlo, které by funkce optimalizované kompilátorem importovalo. Plán na hodinu. Výsledkem je zcela nový parser PHP, nový nástroj na kontrolu a opravu kódu DressCode a k tomu balíčky pro upgrade Nette, Symfony a Laravelu.

Proč nestačí PHP CS Fixer a PHP_CodeSniffer

PHP CS Fixer i PHP_CodeSniffer používáme roky a fungují. Jejich slabina se ukáže až ve chvíli, kdy do nich chcete napsat vlastní pravidlo. Oba čtou kód přes token_get_all(), tedy jako dlouhý plochý seznam tokenů. Ten neví, kde končí if, ani jestli & znamená referenci, bitový součin nebo průnik typů. Nástroje proto nad tokeny doplňují informace o struktuře kódu a nabízejí pomocné metody. Velkou část pravidla ale pořád tvoří kód, který zjišťuje, co právě kontroluje a zda je bezpečné do toho zasáhnout.

Podle Davida je to vidět na číslech. Pravidlo ReferenceUsedNamesOnly ze Slevomat Coding Standard má skoro tisíc řádků a využívá 41 pomocných tříd s celkem zhruba 17 000 řádky. Každá nová syntaxe PHP pak znamená opravovat spoustu pravidel: podporu property hooks z PHP 8.4 se PHP CS Fixer učil 227 dní a PHP_CodeSniffer ji podle Davida v září 2026 neuměl pořád.

Dřív by to byla nepříjemnost. Dnes ale kód čím dál častěji píše agent, a jak zaznělo v přednášce, agenta musíte zkrotit. Nasadit mu postroj, klapky na oči. Od toho je tu linter, který ho klepne přes prsty, a fixer, který po něm kód hezky učeše. Nástroj, který se novou syntaxi učí přes sedm měsíců, se do takové smyčky nehodí.

Parser, který fakt nic nezahodí

Klasický PHP parser, jako je nikic/PHP-Parser, staví abstraktní syntaktický strom (AST). Pro analýzu kódu je to ideální: AST zachycuje jeho strukturu a odhlíží od detailů zápisu, jako jsou mezery, prázdné řádky nebo nadbytečné závorky. Komentáře také nejsou samostatnými uzly tohoto stromu. Při běžném zápisu upraveného AST zpátky do PHP proto podobu kódu určuje tiskárna parseru. PHP-Parser sice umí tisk se zachováním formátu pomocí původních tokenů, ale změněné části může přetisknout po svém.

David proto napsal PhpSyntax, parser, který staví konkrétní syntaktický strom (CST). Každý token v něm má své místo a mezery s komentáři visí na tokenech. Nezačínal přitom na zelené louce: parser PHP výrazů už měl hotový v Latte. Celý slib knihovny se vejde na jeden řádek:

Printer::print($parser->parse($code)) === $codeCode language: PHP (php)

Po naparsování a opětovném vytištění je soubor bajt po bajtu shodný s originálem. Když tedy přejmenujete volání funkce, diff obsahuje jen přejmenovaná volání:

use PhpSyntax\Nodes\Expression\FunctionCallNode;
use PhpSyntax\Nodes\NameNode;
use PhpSyntax\Parser;
use PhpSyntax\Printer;

$file = new Parser()->parse(file_get_contents('Order.php'));

foreach ($file->find(FunctionCallNode::class) as $call) {
	if ($call->name instanceof NameNode && $call->name->text === 'sizeof') {
		$call->name->text = 'count';
	}
}

file_put_contents('Order.php', Printer::print($file));Code language: PHP (php)

Zadarmo to není. Podle měření autora trvá parsování zhruba 1,5× déle než s PHP-Parserem. Pokud potřebujete zjistit, co kód dělá, nebo parsovat soubor se syntaktickou chybou, zůstaňte u PHP-Parseru. Pokud kód měníte a zbytek chcete nechat na pokoji, je PhpSyntax přesně ono. A právě na něm stojí DressCode.

Instalace

DressCode je nástroj, ne knihovna, takže ho nainstalujeme globálně (stačí mít globální binárky Composeru v PATH). Rovnou sáhneme po vývojové verzi, která obsahuje všechno, o čem je tenhle článek:

composer global require dresscode/dresscode:dev-masterCode language: PHP (php)

Veřejně dostupná verze 1.3.0 totiž za dokumentací momentálně zaostává: příkazy init, import a upgrade ani standardy symfony a laravel v ní ještě nejsou a přijdou až s verzí 1.4. Jen se nelekněte čísla verze, hlavička výpisu hlásí 1.3.0 i u vývojové verze.

Pozor ještě na verzi PHP. DressCode sám potřebuje PHP 8.4 až 8.6, ale kontrolovat umí kód psaný pro PHP 8.0 a novější. Cílovou verzi si přečte z require.php ve vašem composer.json, takže do projektu pro PHP 8.1 nikdy nenapíše syntaxi z 8.4.

První kontrola

DressCode nemá vlastní styl, a bez konfigurace proto odmítne začít. Kdyby si potichu vybral nějaký standard, první oprava by vám přepsala půl projektu. Nejrychlejší je nechat nástroj zjistit používaný styl z existujícího kódu:

dresscode init

Pustil jsem ho na kód naší aplikace Testomato:

Sample     261 of 1305 files in bin, db, etc, phinx.php, public, src, tests, 3 ignored by git left out
Standard   perCs, not chosen by measure; the dry runs below count what each would change, not which is nearest, and --use writes another
Indent     tab 100% of 255 files
Quotes     single 100%, double 0% of 10127 strings
Conditions apart 100% of 5 conditions
Namespaces 338 functions and 35 constants declared, resolved for certain
Dry run    perCs      94 of 100 sampled files would change, the one written
           psr12      95 of 100
           nette      97 of 100
           laravel    98 of 100
           symfony   100 of 100

dresscode.neon written, made to measure.Code language: CSS (css)

DressCode se podíval na vzorek jedné pětiny souborů. Zjistil, že odsazujeme výhradně tabulátorem a používáme jen jednoduché uvozovky. To zapsal do konfigurace a jako základ použil standard perCs, což pro nás byla správná volba.

Od té chvíle stačí pustit dresscode check. U nás vypadal výpis takhle (hodně zkrácený):

Config     /Volumes/Work/testomato/app/dresscode.neon
Target     PHP 8.5 from `composer.json`
Checking   1305 files in /Volumes/Work/testomato/app
...
db/migrations/20261001120000_rename_notifications_collection.php
  error   12:78  Expected a line break before the opening brace.                                                       braces.position.class
  error   13:23  The constant `From` must be written in UPPER_CASE.                                                    naming.constant
  error   14:23  The constant `To` must be written in UPPER_CASE.                                                      naming.constant
  error   18:29  Expected a line break before the opening brace.                                                       braces.position.function
  error   29:19  Expected a line break before the array item, because the line is 129 characters long, more than 120.  multiline.items.array
                 Then also: A multi-line array must end with a trailing comma.                                         multiline.trailingComma.array
...
1093 violations have no fix, the rules only report them.

FOUND  17411 violations, 7781 of them following from others, 520 warnings, a fix leaves 573 in 1206 of 1305 files, 1.6 sCode language: JavaScript (javascript)

To jsem tak nějak čekal. perCs sice používáme, ale jen jako základ, ze kterého vycházíme.

Každý řádek říká, kde je problém (řádek a sloupec), jak má kód vypadat a kterému nastavení neodpovídá. Řádek Then also je porušení, které by přinesla teprve sama oprava. Nejzajímavější je sloupec vpravo. braces.position.function není jméno pravidla, ale klíč konfigurace. Právě ten nastavíte, potlačíte nebo si ho necháte vysvětlit:

dresscode explain braces.position.functionCode language: CSS (css)
DRESS|CODE 1.3.0
Config     /Volumes/Work/testomato/app/dresscode.neon
Target     PHP 8.5 from composer.json

braces.position.function

Where the { of a function and of a method whose parameters stand on one line stands, the { of a property hook and of the list of hooks staying on the line of its head; the body starts a line below it and the } takes a line of its own.

By default keep.

Takes: sameLine (on the line of what comes before); nextLine (on the next line); keep

Here sameLine, set by the configuration.

The standards: perCs nextLine, psr12 nextLine, nette nextLine, symfony nextLine, laravel nextLine.

Rule DressCode\Rules\Whitespace\BracesPositionRule, stage Formatting.

See https://dresscode.run/decisions/braces.position.functionCode language: JavaScript (javascript)

Výpis vysvětlí, co klíč ovládá a jaké hodnoty přijímá. U nás platí sameLine z vlastní konfigurace, zatímco všechny uvedené standardy používají nextLine. Rovnou tedy vidíte i naši odchylku od standardu.

Těch 17 411 porušení vypadá hrozivě, ale složka tests se u nás dřív vůbec nekontrolovala. Nově jsem ji do kontrol zahrnul a chyby v ní odladil. Po doplnění chybějících pravidel a našich odchylek od perCs jsme se dostali na:

FOUND  527 violations, 1 of them following from others, 4 warnings, a fix leaves none in 119 of 1305 filesCode language: JavaScript (javascript)

Konfigurace je čitelná a má nádherných 170 řádků 😍. Ta předchozí byla zhruba dvakrát delší. Takhle vypadá její část:

# ...
naming:
	class: keep
	method: keep
	constant: keep
	enumCase: keep

expressions:
	comparison:
		yoda: forbidden
		loose: forbidden
		notEquals: '!='
	coalesce: required
	assignment:
		increment: postfix

hygiene:
	is_null: forbidden
	conversionFunction: forbidden
	separateIssets: forbidden
	separateUnsets: forbidden
# ...

Pokud chcete zapnout zjišťování typů přes PHPStan, přidejte do konfigurace typeAnalysis: phpstan. Pro tuto integraci musí být DressCode nainstalovaný přímo v projektu vedle PHPStanu (composer require --dev dresscode/dresscode:dev-master phpstan/phpstan); globální instalace pro tuhle integraci nestačí. Projekt pak musí splňovat i požadavky DressCode na verzi PHP.

Hodnoty rovnou říkají, co od kódu chceme: forbidden zakazuje, required vyžaduje a '!=' ukazuje konkrétní zápis operátoru. A keep říká, že tady nic nevynucujeme a kód zůstane, jak je.

Pokud chcete nástroj jen vyzkoušet a nic nezakládat, standard zvolíte přepínačem:

dresscode check src tests --use perCsCode language: PHP (php)

Když najde porušení, skončí check s exit kódem 1, takže v CI funguje bez dalšího nastavování.

Oprava

Před opravou mějte čistý pracovní strom, ať v diffu vidíte jen to, co udělal nástroj. Co se změní, uvidíte předem přes dresscode check --diff. Pak už jen:

dresscode fix

Opravu commitněte zvlášť, bez jiných změn. Takový commit nikdo nečte řádek po řádku a formátování se vám nesmíchá se skutečnou prací. Když jeho hash přidáte do souboru .git-blame-ignore-revs, GitHub ho v blame přeskočí. Lokálně k tomu stačí git config blame.ignoreRevsFile .git-blame-ignore-revs. Co oprava nezvládne, třeba přejmenovat metodu volanou jinde v projektu, zůstane ve výpisu jako error. Je-li toho na jeden zátah moc, zapíšete to do baseline a hlásit se budou jen nové problémy.

Konfigurace, která se dá přečíst

U PHP CS Fixeru vyjmenováváte pravidla a jejich volby. DressCode na to jde obráceně: klíč pojmenuje věc v kódu a hodnota říká, jak se ta věc píše. Žádná true, false, false, true kouzla.

use:
	- perCs
	- cleanup
	- modernizations

braces:
	position:
		class: nextLine
spacing:
	concatenation: '$a.$b'Code language: PHP (php)

Konfigurace se skládá ze čtyř vrstev:

  • Standard – jeden základ: psr12, perCs, nette, symfony nebo laravel.
  • Sady – přidáte je podle toho, co od kódu chcete: cleanup, modernizations, types, deprecations, correctness a compilerOptimizations.
  • Vlastní klíče – odchylky od standardu. Hodnota keep znamená „tady nic nevynucuj“.
  • Přepisy – jiné hodnoty pro část projektu, třeba pro tests/.

Formát je NEON a klíče jako paths nebo excludePaths jsou schválně stejné jako u PHPStanu. Stávající konfiguraci PHP CS Fixeru převede dresscode import.

Když oprava udělá něco, co jste nečekali, koukněte do dresscode config. Vypíše, která rozhodnutí nakonec platí a ze které vrstvy se každá hodnota vzala.

Se sadou modernizations stačí zvednout verzi PHP v composer.json a DressCode přepíše kód tak, aby využil novinky nové verze. Například ternární výraz, který jen kontroluje null:

return $article === null ? null : $article->getAuthor();Code language: PHP (php)

přepíše na výraz s nullsafe operátorem z PHP 8.0:

return $article?->getAuthor();Code language: PHP (php)

Podle měření autora je takový přepis mnohonásobně rychlejší než Rector: nad Symfony 3.4 trvá 1,6 s proti 128,5 s.

Zpátky k těm lomítkům

PHP před spuštěním přeloží kód do instrukcí (opcodů) a u zhruba tří desítek funkcí jako strlen(), count() nebo is_array() umí volání nahradit jedinou instrukcí. Má to ale háček: kompilátor musí s jistotou vědět, kterou funkci voláte. Ve jmenném prostoru App totiž holé strlen() může znamenat i App\strlen(), a tak PHP vyrobí obecné volání, které se rozhodne až za běhu. Kompilátor má jistotu, kterou funkci voláte, jen při použití zpětného lomítka, importu use function nebo v globálním jmenném prostoru.

Podle měření v dokumentaci trvá 20 milionů volání strlen($s) ve jmenném prostoru 384 ms, s lomítkem 165 ms. Optimalizaci přitom vypne i pojmenovaný argument, takže in_array($x, $list, strict: true) je pomalejší než stejné volání s obyčejným true. Hlídat tohle ručně by nikoho nebavilo, a proto tu je sada compilerOptimizations:

use:
	- nette
	- compilerOptimizations

nameResolution: certain
Code language: PHP (php)

Klíč nameResolution: certain říká, že ve jmenných prostorech nedeklarujete žádné funkce ani konstanty kromě těch, které vyjmenujete v klíči namespaces. Bez něj DressCode tyhle úpravy považuje za rizikové a jen je ohlásí. Vezměme třeba tuhle třídu:

namespace App;

class Access
{
	public function isAllowed(array $roles, string $role): bool
	{
		if (PHP_VERSION_ID < 80400) {
			trigger_error('Old PHP', E_USER_DEPRECATED);
		}

		return in_array($role, $roles, strict: true) && strlen($role) > 2;
	}
}
Code language: PHP (php)

DressCode přidá deklarace use a upraví volání in_array():

namespace App;

use function strlen;
use const PHP_VERSION_ID;

// ...

		return in_array($role, $roles, true) && strlen($role) > 2;
Code language: PHP (php)

Díky use PHP už při překladu ví, že jde o globální funkci nebo konstantu, a nemusí za běhu zjišťovat, jestli náhodou neexistuje App\strlen() nebo App\PHP_VERSION_ID.

Zázraky ale fakt nečekejte. Úspora je v nanosekundách na jedno volání a webová aplikace stejně většinu času čeká na něco jiného, například na připojení k databázi. Je to mikrooptimalizace zdarma, tak proč ne.

DressCode jako postroj (harness) pro agenta

Právě tady má podle mě DressCode největší smysl. Do Claude Code ho podle dokumentace přidáte dvěma příkazy:

/plugin marketplace add nette/agent-plugins
/plugin install php-fixer@nette

Do Codex CLI nainstalujete tentýž plugin také přes marketplace:

codex plugin marketplace add nette/agent-plugins
codex plugin add php-fixer@nette

Pak spusťte novou relaci a přes /hooks zkontrolujte a schvalte hooky pluginu. Samotná instalace je ještě neaktivuje. Pokud DressCode nemáte v projektu ani globálně, spusťte instalační skill install-php-fixer, který plugin nabízí. Podrobný postup najdete v README pluginu.

Po podporovaných editacích PHP souborů pak plugin pustí DressCode s konfigurací projektu, a kde žádná není, použije standard Nette. V Claude Code jde o nástroje Edit a Write, v Codexu o apply_patch. Při změnách provedených přes shell se hook nespustí; po nich je potřeba kontrolu zavolat zvlášť.

Výstup je psaný pro model, ne pro člověka. Agent z něj zjistí, co se v souboru změnilo, proč některé importy zmizely a jak vyřešit zbývající problémy. Když je soubor v pořádku, DressCode mlčí, takže agentovi zbytečně neplní kontext a nežere tokeny.

Šikovné je, že ve starém souboru opraví jen to, co pokazil agent. Zbytek nechá, jak byl, takže v commitu najdete jen změny, které agent opravdu udělal. A protože je zhruba dvakrát až třikrát rychlejší než PHP CS Fixer (podle dokumentace 118 ms proti 323 ms u malého souboru), agenta po editaci nijak nezdržuje.

Upgrade Nette, Symfony nebo Laravelu

Tohle je nečekaný bonus, který leze do zelí Rectoru. Nástroj, který kódu rozumí, se nemusí zastavit u formátování a doplňování mezer. Po zvednutí verzí knihoven v composer.json přepíše kód na jejich nové API:

composer require --dev \
  dresscode/dresscode:dev-master \
  dresscode/upgrade-nette:dev-master \
  phpstan/phpstan
vendor/bin/dresscode upgradeCode language: JavaScript (javascript)

Balíček s daty si vyberte podle svého ekosystému:

  • upgrade-nette – Nette
  • upgrade-symfony – Symfony
  • upgrade-laravel – Laravel
  • upgrade-deegee – Dibi a Texy

Všechny mají zatím jen vývojovou verzi, stejně jako příkaz upgrade. Proto u nich i u DressCode stojí :dev-master. Jinak by Composer stáhl DressCode 1.3.0, který upgrade ještě nezná.

Data o tom, co se mezi verzemi změnilo, podle Davida vygenerovali agenti, kteří prošli historii úprav jednotlivých frameworků a knihoven commit po commitu. Bez PHPStanu upgrade nepoběží, protože se ho ptá na typy: getName() přepíše jen tam, kde se opravdu volá na FileUpload, a nikde jinde. Na styl kódu přitom nesáhne. Pouštějte ho opakovaně, dokud něco přepisuje, protože jedna úprava může zpřístupnit další navazující přepis.

Závěrem

Z jednoho pravidla pro use function vyrostl bezztrátový parser PHP a formátovač s konfigurací, která se dá číst. Znalost struktury kódu zjednodušuje psaní pravidel. A když díky use function PHP už při překladu ví, že voláte globální funkci, nemusí ji za běhu hledat nejprve v aktuálním jmenném prostoru a pak v globálním. I tím se dá ušetřit trochu času.

DressCode je zatím v plenkách, ale já mu fakt moc fandím. Kdo někdy ladil konfiguraci PHP CS Fixeru nebo PHP_CodeSnifferu přes desítky true a false, pochopí, že to fakt nemá budoucnost.

Nejrychleji DressCode do projektu dostanete tak, že nasazení necháte na agentovi. Stačí použít tenhle prompt:

Nasaď do projektu DressCode (https://dresscode.run/cs/):

1. Nainstaluj globálně verzi dev-master.
2. Spusť `dresscode init` a `dresscode check`.
3. Uprav dresscode.neon tak, aby odpovídal našemu stylu, a ukaž mi ho.
4. Po schválení spusť `dresscode fix` a změny commitni zvlášť.Code language: JavaScript (javascript)

A pokud chcete, aby DressCode hlídal kód průběžně při práci agenta, přidejte ještě plugin podle postupu v sekci o agentech.

Zdroje

Komentáře

Odebírat
Upozornit na
guest
0 Komentářů
Nejstarší
Nejnovější Nejvíce hlasů

Spacer.gif a tabulky v tabulkách: Jak se kdysi dělaly webové stránky

Kdo dnes napíše display: grid a za minutu má hotový třísloupcový layout, těžko si představí, jakou akrobacii to stálo před pětadvaceti lety. Webové stránky tehdy držely pohromadě vnořené tabulky, průhledné obrázky o velikosti jednoho pixelu a pevná víra, že to nikdo neotevře v jiném prohlížeči. Pojďme si připomenout, jak to fungovalo, proč to fungovalo a proč jsme s tím museli skončit.