PHP API
Jak DressCode spustit z vlastního kódu: načtení konfigurace, Runner, výsledky běhu a vlastní
reporter, který si výstup zpracuje po svém.
Kdy sáhnout po API
Příkazová řádka pokryje průběžnou integraci i Git hooky. API potřebujete tehdy, když DressCode zapojujete do vlastního nástroje: do generátoru, který má vyrobený kód rovnou naformátovat, do migračního skriptu, do služby, která kontroluje kód z formuláře, nebo když chcete výsledky v podobě, kterou žádný z vestavěných formátů nedává.
Běh nad projektem
Konfigurace se načte stejně jako z příkazové řádky, z ní se postaví Runner a ten dostane seznam
souborů a reporter:
use DressCode\Config\Loader;
use DressCode\Config\RunnerFactory;
use DressCode\Reporters\JsonReporter;
[$config, $root, $configFile] = new Loader()->load(file: null, directory: getcwd());
$runner = new RunnerFactory()->createRunner($config, $root, configFile: $configFile);
$files = $runner->findFiles(['src', 'tests']);
$result = $runner->run($files, fix: false, reporter: new JsonReporter(STDOUT));
exit($result->getExitCode());
Loader::load() najde dresscode.neon nebo dresscode.php (případně jejich šablonu
s příponou .dist) od zadaného adresáře směrem nahoru, nebo vezme soubor, který mu určíte, a vrátí
trojici: konfiguraci, kořenový adresář projektu a cestu ke konfiguračnímu souboru. Bez konfiguračního souboru platí
konfigurace, kterou předáte třetím argumentem, a cesta je null; když žádnou nepředáte, vyhodí
ConfigurationException. Runner::findFiles() rozvine předané cesty na soubory podle vyloučení
(i těch z rozšíření) a přípon z konfigurace, přičemž soubor zadaný jménem vezme tak, jak je, pokud nepředáte
skipExcluded: true; klíč paths nečte, ten zpracovává příkazová řádka, takže cesty
předáváte sami. run() soubory zpracuje a výsledky posílá reporteru soubor po souboru. Pravidlo nebo analýzu,
kterou konfigurace staví přes closure (anonymní funkci), popisuje jen text konfiguračního souboru, a proto
createRunner() takový běh cachuje, jen když dostane cestu k tomu souboru v argumentu
configFile.
Runner a RunnerFactory jsou označené @internal, stejně jako všechno
ostatní ve jmenném prostoru DressCode\Config kromě Loader, takže se jejich podoba může
v minoritní verzi změnit. Pro spuštění běhu jsou stabilní Loader, konfigurace (Config,
Profile, Override, Group), rozhraní Reporter s výsledky
FileResult a RunResult, porušení Violation se závažností Severity,
vestavěné reportery, celý příkaz v Console\Application a výjimka
Console\UsageException, kterou hlásí chybné volání. Stabilní je i všechno, co potřebujete k psaní pravidel. Pokud chcete, aby váš nástroj fungoval beze změny
i s příští minoritní verzí, spouštějte běh přes Console\Application místo Runner.
RunResult má ve vlastnosti files výsledek FileResult pro každý soubor, jen bez kódu
a výstupu, aby velký projekt nezůstal celý v paměti (ty dostane reporter v reportFile()). Dál nese
fix (šlo o opravu), warnings (varování k celému běhu, třeba o zastaralých položkách
baseline), maxWarnings (práh z --max-warnings) a souhrnná čísla: countViolations()
(porušení souborů, jak byly načtené), countLeftAfterFix() (kolik jich po opravě zbude, v check
i ve fix), countRemaining() (co zůstalo na uživateli: v check všechna porušení, ve
fix ta, která oprava nechala), countDerived() (kolik z nich plyne z opravy jiného porušení),
countRiskyDeferred() (rizikové opravy, které čekají na svolení) a listRiskyDeferredRules() (jména
jejich pravidel), countChangedFiles(), countSyntaxErrors() (soubory, které nejdou parsovat),
countFailures() (soubory, kde pravidlo selhalo), vlastnost baselined s počtem porušení, která
umlčela baseline, a getExitCode() podle stejných pravidel jako na příkazové řádce. První čtyři počty
přijmou volitelnou závažnost, Severity::Error nebo Severity::Warning.
Kdo konfiguraci staví v PHP místo načítání ze souboru, napíše new Config(...) se stejnými klíči jako
NEON a přepisy pro část projektu jako objekty Override. Metoda createRunner() přijme navíc profil
commandLine, který leží nad vším jako přepínače --preset, --group a
--rule, a seznam only, který běh zúží jako --only:
use DressCode\Config;
use DressCode\Config\RunnerFactory;
use DressCode\Group;
use DressCode\Override;
use DressCode\Profile;
$config = new Config(
presets: ['nette'],
groups: [Group::OptimizedCalls],
overrides: [new Override(['tests'], nameResolution: 'uncertain')],
paths: ['src', 'tests'],
);
$runner = new RunnerFactory()->createRunner($config, getcwd(), commandLine: new Profile(rules: ['line-length' => false]));
Jeden soubor nebo řetězec
Pro kód, který neleží na disku, nebo pro jeden soubor bez hledání:
$result = $runner->processFile('src/Cart.php', $code);
foreach ($result->violations as $violation) {
echo "$violation->line: $violation->message ($violation->ruleName)\n";
}
$fixed = $result->output;
processFile() nikdy nezapisuje. Cesta říká, která pravidla pro kód platí (podle ní se vyhodnotí přepisy
pro cesty), $result->output je opravený kód, $result->isChanged() řekne, jestli se od vstupu
liší, a $result->remaining jsou porušení, která opravený kód pořád má. Kód, který nejde naparsovat,
má output rovný null a chybu v error. Když pravidlo selže nebo se pravidla zacyklí,
vyhodí processFile() výjimku RuleException nebo ConvergenceException. Naproti tomu
processPath() soubor přečte, s fix: true ho i zapíše a selhání pravidla místo výjimky zapíše
do failure výsledku, stejně jako to dělá run(). Ani jedno nepoužívá cache.
FileResult nese cestu, původní kód a výstup (výsledky, které si nechá RunResult, je už
nemají, reporter je dostane v reportFile()), seznam objektů Violation nalezených v původním kódu
a seznam remaining s porušeními výstupu a s pozicemi v něm, tedy přesně s tím, co ohlásí příští
kontrola opraveného souboru. Dál nese varování warnings, chybu parsování error s řádkem
errorLine, selhání pravidla failure, počet průchodů passes, otisky porušení
umlčených baseline baselined a příznaky written (soubor se zapsal) a cached (cache ho
znala jako čistý). Violation má pravidlo, zprávu, řádek, sloupec, závažnost, risky (oprava je
riziková), refused (běh ji neudělal, protože k ní nebylo svolení), otisk pro baseline a
derivedFrom, otisk porušení, z jehož opravy plyne.
Vlastní reporter
Reporter je rozhraní se třemi metodami. Výsledky mu chodí v pořadí vstupu, shrnutí na konci:
use DressCode\FileResult;
use DressCode\Reporter;
use DressCode\RunResult;
final class CountingReporter implements Reporter
{
private array $byRule = [];
public function start(int $fileCount, bool $fix): void
{
}
public function reportFile(FileResult $result): void
{
foreach ($result->violations as $violation) {
$this->byRule[$violation->ruleName] = ($this->byRule[$violation->ruleName] ?? 0) + 1;
}
}
public function finish(RunResult $result): void
{
arsort($this->byRule);
foreach ($this->byRule as $rule => $count) {
printf("%5d %s\n", $count, $rule);
}
}
}
Takový reporter po prvním běhu nad starým projektem řekne, která tři pravidla dělají devadesát procent všech
porušení, a to je přesně informace, podle které se rozhoduje, co vypnout a co opravit. Vestavěné reportery
(ConsoleReporter, JsonReporter, CheckstyleReporter, GithubReporter) jsou
dobrý vzor k nahlédnutí. NullReporter nevypíše nic a hodí se tam, kde vás zajímá jen vrácený
RunResult.
Vlastní příkaz
Kdo balí DressCode do vlastní binárky (tak vznikl příkaz ecs v Nette Coding Standardu), spustí v procesu
Console\Application a dá jí konfiguraci, která platí, když projekt žádnou vlastní nemá:
use DressCode\Config;
use DressCode\Console\Application;
$application = new Application(defaultConfig: new Config(extensions: [Nette\CodingStandard\Extension::class], presets: ['nette']));
exit($application->run($argv));
Všechno ostatní, tedy příkazy, přepínače, formáty i paralelní běh, zůstává na příkazové řádce. Chybné
volání (neznámý přepínač nebo Console\UsageException), špatnou konfiguraci
(ConfigurationException), selhání pravidla (RuleException), zacyklená pravidla
(ConvergenceException) a chyby prostředí typu RuntimeException, třeba soubor, který nejde zapsat,
nebo spadlý pracovní proces, run() zachytí sám, vypíše je na standardní chybový výstup a vrátí exit kód
2. Ostatní výjimky, třeba LogicException nebo Error, znamenají chybu v programu a
run() je nechá propadnout ven.