Z PHP CS Fixeru na DressCode
Sady @PSR12, @PER-CS i @Symfony mají v DressCode své standardy a celý
.php-cs-fixer.dist.php přeloží na dresscode.neon příkaz dresscode migrate config.
Místo fixerů a jejich voleb pak píšete, jak má kód vypadat: kde PHP CS Fixer potřebuje na lomítka před globálními
jmény dva fixery a šest voleb, stačí tu jedna sekce. A řada rizikových (risky) pravidel tu riziková není, protože
bezpečný případ od nebezpečného rozezná syntaktický strom.
DressCode jako alternativa k PHP CS Fixeru
V každodenní práci se změní málo: check nahradí fix --dry-run a fix zůstává
fix. Přibude upgrade kódu: týž dresscode fix umí kód převést i na novější PHP a nové verze
Nette, Symfony nebo Laravelu.
V Laravelu, který PHP CS Fixer zformátoval svou sadou pro PER Coding Style, našel DressCode ještě 1 201 míst, kde kód standard nedodržuje, a v Symfony 3.4 jich bylo 7 497. Část rozdílu jde na vrub verzím: PHP CS Fixer implementuje PER Coding Style 3.0, DressCode verzi 3.1. Opačným směrem změnil PHP CS Fixer po DressCode v Laravelu 9 míst; odkud se vzala, vysvětluje srovnání. Měřeno v říjnu 2026 s PHP CS Fixerem 3.95.
Projektu, který musí zůstat na PHP 7, poslouží líp PHP CS Fixer: běží i na PHP 7.4. DressCode sám potřebuje PHP 8.4 a kontroluje kód psaný pro PHP 8.0 a novější.
Než začnete
Společný postup platí pro všechny nástroje; tady je jen to, co je u PHP CS Fixeru jinak.
Nejdřív to hlavní: konfigurace PHP CS Fixeru je PHP soubor, který se musí spustit a který vrací objekt jeho
knihovny. Příkaz dresscode migrate config ho proto spustí v samostatném procesu s načteným autoloaderem
projektu, takže co soubor vypíše nebo udělá, zůstane v tom procesu. DressCode přitom může být nainstalovaný kdekoli,
v projektu ale musí ještě zůstat friendsofphp/php-cs-fixer. Soubor se spouští jen kvůli pravidlům; cesty
z něj DressCode přečte bez spuštění. Když třídy Fixeru chybí, migrate config skončí chybou konfigurace
s exit kódem 3:
Error: File `/var/www/shop/.php-cs-fixer.dist.php` needs the classes of PHP CS Fixer, which the autoload of its project does not load: Class "PhpCsFixer\Finder" not found.
Konfiguraci proto překládejte dřív, než Fixer z projektu odeberete.
Převod konfigurace
dresscode migrate config .php-cs-fixer.dist.php
Sady se překládají na standardy: @PSR1, @PSR2 a @PSR12 na psr12,
@PER, @PER-CS a jejich číslované varianty na perCs, který odpovídá PER Coding Style
3.1, a @Symfony na symfony, který zatím nemá většinu pravidel o phpDocu. Sady bez protějšku
migrate config ohlásí a řekne, odkud začít; u @PhpCsFixer je to symfony. Sady
s příponou :risky protějšek nemají, protože rizikové opravy (risky fixes) tu nezapíná sada, ale projekt
u jednotlivých rozhodnutí (viz níže).
Fixery se překládají na rozhodnutí, a to i s volbami, pokud mají protějšek. Z konfigurace se sadou
@PER-CS, fixerem no_unused_imports a trailing_comma_in_multiline s elements
pro pole, argumenty a parametry vyjde tenhle dresscode.neon:
use: [perCs]
imports:
unused: forbidden
multiline:
trailingComma:
array: required
argument: required
parameter: required
matchArm: optional
closureUse: optional
import: optional
list: optional
classes:
instantiationWrapping: keep
no_unused_imports je imports.unused, které
stejně jako fixer počítá za použití i jméno v phpDocu. trailing_comma_in_multiline se rozložil do klíčů
multiline.trailingComma, jeden na každé místo, kde
čárka může stát; co v elements nebylo, dostalo optional, tedy čárku nechat, jak ji autor napsal.
classes.instantiationWrapping: keep přidal migrate config sám: standard perCs píše od
PHP 8.4 new Foo()->bar() bez závorek kolem new, kdežto sada @PER-CS je nechává, jak
jsou. Fixer, který protějšek nemá, se objeví ve výpisu na chybovém výstupu a z toho máte seznam věcí, o kterých je
potřeba rozhodnout. Vlastní fixery z balíčku kubawerlos/php-cs-fixer-custom-fixers se překládají také, pokud
jejich konstrukci DressCode zná.
Co migrate config nepřenese, protože to v konfiguraci Fixeru nejsou pravidla:
- Finder, kromě cest.
in(),exclude(),notPath(),notName()aappend()s literálem nebo__DIR__přečtemigrate configz kódu konfigurace a zapíše dopathsaexcludePathssám.filter(),depth()a další podmínky Finderu, i volání, jehož argument se bez spuštění přečíst nedá, třebain($dirs), vypíše mezi varováními; ty přepište ručně, vzory popisuje Konfigurace. - Odsazení a konce řádků (
setIndent(),setLineEnding()). Nastaví je standard. Pokud žádný nepoužíváte, doplňte rozhodnutíindentation.unitafile.lineEnding, na která vásmigrate configbez standardu sám upozorní. setRiskyAllowed(true). Svolení k rizikovým opravám nedává DressCode celé konfiguraci, ale jednotlivým rozhodnutím: jejich cesty patří do seznamufixRisky. Bez něj se rizikové opravy jen hlásí.
Jedna věc, jeden klíč
PHP CS Fixer má pravidla, která se o jednu konstrukci dělí, a výsledek vznikne z jejich souhry. Projekt postavený na
PER Coding Style, který chce globální třídy psát s lomítkem, \Exception, a funkce bez něj, kromě těch,
které PHP umí optimalizovat, potřebuje dva fixery a šest voleb, z nichž jedna říká opak toho, co čtete:
import_classes => false neznamená „neimportovat“, ale „importované třídy přepsat na celé jméno“.
K tomu no_unused_imports, aby zmizely importy, které po přepisu zbudou:
$config->setRules([
'@PER-CS' => true,
'native_function_invocation' => [
'include' => ['@compiler_optimized'],
'scope' => 'namespaced',
'strict' => true,
],
'global_namespace_import' => [
'import_classes' => false,
'import_constants' => false,
'import_functions' => false,
],
'no_unused_imports' => true,
]);
V DressCode jsou to klíče jedné sekce, qualification, a každý jmenuje, co chce; nepoužité
importy smaže imports.unused:
use: perCs
qualification:
class:
global: backslashed
function:
global: bare
optimized: backslashed
imports:
unused: forbidden
Z tohohle kódu:
<?php
namespace App\Model;
use DateTimeImmutable;
use InvalidArgumentException;
class Cart
{
public function add(array $items, string $sku): DateTimeImmutable
{
if (strlen($sku) === 0 || in_array($sku, $items, true)) {
throw new InvalidArgumentException('Invalid SKU');
}
return new DateTimeImmutable(\date('Y-m-d'));
}
}
udělají oba nástroje tohle:
<?php
namespace App\Model;
class Cart
{
public function add(array $items, string $sku): \DateTimeImmutable
{
if (\strlen($sku) === 0 || in_array($sku, $items, true)) {
throw new \InvalidArgumentException('Invalid SKU');
}
return new \DateTimeImmutable(date('Y-m-d'));
}
}
Až na jedno místo: in_array($sku, $items, true) PHP CS Fixer kvalifikuje, protože in_array je
v seznamu @compiler_optimized, kdežto DressCode ho nechá holé, protože kompilátor zrychlí jen hledání
v poli zapsaném přímo v kódu, ne v proměnné. DressCode rozhoduje podle volání, ne podle jména. Lomítko u
date() odebere bez rizika, když konfigurace řekne nameResolution: certain; jinak je to riziková
oprava, stejně jako je native_function_invocation rizikový fixer. Zbytek sekce, konstanty a importy globálních
jmen, popisuje Jak se píšou
globální jména.
Riziková pravidla
PHP CS Fixer označuje jako rizikové (risky) takové pravidlo, které nad polem tokenů nedokáže odlišit bezpečný
případ od nebezpečného, a rozhodnutí nechává na vás. Typický příklad je ternary_to_elvis_operator:
nepozná, jestli je podmínka ternárního operátoru proměnná, nebo volání funkce s vedlejším účinkem, takže hlídá
jen ++ a -- a za zbytek ručíte vy.
V DressCode tohle rozdělení není, protože otázku zodpoví strom: rozhodnutí expressions.ternary.short zkrátí ternární operátor jen
tehdy, když je podmínku bezpečné vyhodnotit dvakrát, a volání funkce nechá být. Totéž platí pro další pravidla,
která byla riziková z téhož důvodu.
Něco jiného jsou opravy, které mohou změnit chování programu. Ty DressCode nezapíná zvláštní sadou. Hlásí je jako
rizikové místo po místě a u každého řekne, co by o něm rozhodlo: mistakes.strictArgument doplní do
in_array() parametr $strict = true rovnou tam, kde typy z PHPStanu řeknou, že se hledá celé číslo mezi celými čísly, a
jinde opravu jen ohlásí jako rizikovou. Riziková oprava se udělá teprve tehdy, když rozhodnutí uvedete v seznamu
fixRisky v konfiguraci, nebo na jeden běh s --fix-risky; do té doby je to varování, kde by o ní
rozhodly typy, a chyba, kde mění chování ze své podstaty, jako přidání declare(strict_types=1). Napsáním
rozhodnutí, které mění význam kódu, jako expressions.comparison.loose: forbidden, se pro jeho opravy ještě
nerozhodujete; rozhodnutí je až souhlas v fixRisky.
Zvláštní skupinou jsou fixery, které PHP CS Fixer označuje jako rizikové proto, že funkci stejného jména může někdo
deklarovat ve jmenném prostoru: is_null, no_alias_functions, dir_constant a další. Tuhle
nejistotu v DressCode odstraní konfigurace. S klíčem nameResolution: certain a seznamy v klíči
namespaces DressCode ví, co ve vašich jmenných prostorech je, a odpovídající opravy jsou bezpečné; viz Funkce a konstanty ve jmenných prostorech.
Příkazy
| PHP CS Fixer | DressCode |
|---|---|
php-cs-fixer fix --dry-run |
dresscode check |
php-cs-fixer fix --dry-run --diff |
dresscode check --diff |
php-cs-fixer fix |
dresscode fix |
php-cs-fixer fix --config=soubor |
dresscode fix --config soubor |
php-cs-fixer fix --rules=jméno |
dresscode fix --set cesta=hodnota, které rozhodnutí přidá ke konfiguraci, nebo
dresscode fix --only cesta, které pustí jen ta, která jmenujete |
php-cs-fixer fix src --rules=@PSR12 |
dresscode fix src --use psr12 v projektu bez konfigurace |
php-cs-fixer fix --format=checkstyle |
dresscode check -f checkstyle |
php-cs-fixer fix --allow-risky=yes |
dresscode fix --fix-risky; natrvalo seznam rozhodnutí v klíči fixRisky |
php-cs-fixer describe jméno |
dresscode explain cesta |
.php-cs-fixer.cache |
cache je zapnutá sama; --no-cache ji obejde |
Exit kód PHP CS Fixeru je bitová maska (8 znamená nalezená porušení, 16 chybu konfigurace a tak dále), kdežto
DressCode vrací 0 pro čisto, 1 pro porušení, 2 pro selhání na některém souboru a
3 pro chybu v příkazové řádce nebo v konfiguraci. Skript v CI, který masku vyhodnocoval, potřebuje jednu
úpravu.
Co v PHP CS Fixeru nebylo
- PER Coding Style 3.1. PHP CS Fixer implementuje verzi 3.0 (sada
@PER-CS3x0), standardperCsverzi 3.1. Po PHP CS Fixeru najde DressCode nad Laravelem ještě 1 201 míst a nad Symfony 3.4 jich najde 7 497; jsou to místa, kde PER Coding Style chce jednu položku na řádek, heredoc odsazený s okolním kódem nebo čárku za víceřádkovým seznamem, viz srovnání. - Zpráva u každého místa, i s tím, co může oprava změnit.
php-cs-fixer fix --dry-run -vvypíše u souboru jména fixerů, které by ho změnily, a s--diffzměnu samotnou; formátcheckstylemá jednu položku na fixer a soubor, bez čísla řádku. Proč je fixer rizikový, řeknephp-cs-fixer describejednou pro všechna místa, ustrict_paramvětouRisky when the fixed function is overridden or if the code relies on non-strict usage.S--allow-risky=yespak doplnítruedo každéhoin_array(), bez něj do žádného. DressCode s typy z PHPStanu posoudí každé volání zvlášť: kde se hledáintvlist<int>, doplnítruesám, a kde je hledaná hodnotamixed, ohlásí opravu s důvodemRisky because a strict search no longer finds a value of another type.a počká na váš souhlas. Ve stejném výpisu je i přepsaná metoda, jejíž parametr se jmenuje jinak než u rodiče, a volání metody, kterou knihovna označila jako zastaralou; ty PHP CS Fixer nehlásí ani se sadami@PhpCsFixer:riskya@Symfony:risky. Celý výpis ukazuje Začínáme. - Potlačení na řádku. PHP CS Fixer neumí vypnout pravidlo pro jeden řádek ani pro blok, jen pro celý soubor přes
Finder. Tady na to slouží
// dresscode:ignore cestaa dvojicedresscode:disableadresscode:enable, viz Potlačení a baseline. - Jiné hodnoty pro část projektu zapíšete jako přepis v klíči
overrides, bez druhého konfiguračního souboru; rozhodnutí tam dostane jinou hodnotu, nebokeep, viz Konfigurace. - Rozhodnutí na zkoušku bez ztráty konfigurace.
--rulesv PHP CS Fixeru nahradí pravidla z konfiguračního souboru, takže fixer na zkoušku běží bez zbytku vaší sady a s výchozími volbami, pokud mu jiné nevypíšete v JSONu. V DressCodedresscode fix --set cesta=hodnotapřidá rozhodnutí ke zbytku konfigurace, takže jeho změny vyjdou rovnou zformátované podle vašeho standardu, připravené na jeden commit, a--only cestak tomu zúží výpis jen na ně, viz Konfigurace. - Baseline. Rozhodnutí, které váš kód zatím nesplňuje a které za vás opravit nejde, můžete zapnout hned:
dnešní porušení zapíše příkaz
dresscode baselinea hlásit se budou jen nová. PHP CS Fixer ji nemá; jak ji DressCode skloubil s opravami, popisuje Baseline. - Vlastní pravidlo píšete nad stromem, ne nad tokeny. Návod je na jedno odpoledne a kdo má vlastní fixer, přepíše ho podle samostatné stránky.