PHP 8.6 vychází 19. listopadu. DressCode pro něj kód přepisuje už teď. 16 dní Přechod na novější PHP →

Vlastní preset a plugin

Styl, který chcete mít na všech projektech, zabalíte do presetu, vlastní pravidla do pluginu se sekcí rozhodnutí a obojí rozdáte přes Composer. Projekt si to pak zapne jediným řádkem v klíči use, pro celý kód nebo jen pro jeho část.

Preset v NEON

Preset je soubor NEON ve tvaru konfigurace. Nese rozhodnutí, klíč use s tím, z čeho vychází, a případně suppressionComments; nic víc. Takhle vypadá domácí styl firmy Acme:

# Acme house style
#
# PER Coding Style with tabs, lines of up to 100 characters, and the messages of exceptions as sentences.

use: perCs

file:
	lineLength: 100

indentation:
	unit: tab

expressions:
	comparison:
		# a comparison reads in the order it is spoken
		yoda: forbidden

acme:
	exceptionMessagePeriod: required
  • Preset začíná komentářem: na prvním řádku název, pod ním, co preset je. Proč má rozhodnutí svou hodnotu, říká komentář nad jeho klíčem; tak jsou psané i standardy a sady DressCode.
  • use jmenuje presety, ze kterých ten váš vychází, ať standardy, sady, nebo jiné presety. Leží pod vaším presetem v pořadí zápisu a každý jen jednou, takže co preset napíše, přebije to, z čeho vychází, a projekt nad ním přebije preset. Hodnoty se slévají stejně jako v konfiguraci: mapa podle jmen, slovo a seznam nahrazují celou hodnotu, keep u sekce maže všechno pod ní.
  • Rozhodnutí presetu mohou patřit jádru i pluginu, jako acme.exceptionMessagePeriod v ukázce; projekt pak musí mít i plugin, jinak klíč katalog nezná.
  • Co rozhoduje projekt, preset rozhodnout nemůže. Fakty o projektu (targets, typeAnalysis, namespaces, nameResolution), rozsah běhu (paths, excludePaths a další), fixRisky, pluginy a rules nechává projektu. Preset, který by je nastavil, DressCode odmítne s chybou Preset `acme/house` sets `fixRisky`, which the project decides, not a preset. Preset ale smí říct, že se porušení rozhodnutí hlásí jen jako varování, a to klíčem severity, jako file.longLines: warning ve standardu psr12.
  • Preset, který se má dát přidat ke kterémukoli standardu, je sada: o rozvržení kódu nerozhoduje, nevychází ze standardu a nesahá na mezery, odsazení, zalamování ani pořadí. Že je takový, řekněte v jeho úvodním komentáři.

Na verzi PHP ani na to, co má projekt nainstalované, preset myslet nemusí: rozhodnutí, jehož pravidlo si v requires řekne o verzi PHP nebo o balíček, se tam, kde je projekt nemá, tiše neuplatní. Stejně tiše se neuplatní rozhodnutí, jehož pravidlo potřebuje typy, v projektu bez typeAnalysis: phpstan.

Projekt preset použije cestou k souboru v use, třeba use: tools/house.neon, nebo jménem, když ho přinese plugin. Protože je to vrstva jako každá jiná, dá se použít i jen pro část projektu, v přepisu:

use:
	- Acme\CodeStyle\Plugin
	- perCs

overrides:
	-
		paths: [src]
		use: [acme/house]

Plugin s pravidly a presety

Plugin je to, co balíček přinese projektu: třída s rozhraním DressCode\Plugin, jejíž metoda getManifest() vrátí PluginManifest. Ten vyjmenuje pravidla, presety se jmény a soubory, sekci, ve které rozhodují pravidla pluginu, rozhodnutí sdílená víc jeho pravidly, pluginy, na kterých stojí, analýzy, vyloučené cesty, filtr souborů skipWhen a adresu stránek pravidel ruleUrl. Takhle vypadá plugin balíčku firmy Acme:

namespace Acme\CodeStyle;

use DressCode\PluginManifest;


final class Plugin implements \DressCode\Plugin
{
	public function getManifest(): PluginManifest
	{
		return new PluginManifest(
			rules: [ExceptionMessagePeriodRule::class, NoDebugCallsRule::class],
			presets: ['acme/house' => __DIR__ . '/house.neon'],
			section: 'acme',
			excludePaths: ['generated'],
			ruleUrl: 'https://acme.dev/rules/{slug}',
		);
	}
}

Sekce je jméno pluginu v konfiguraci. Všechna rozhodnutí jeho pravidel leží pod ní, acme.exceptionMessagePeriod a acme.debugCalls, takže se nesrazí s jádrem, s jiným pluginem ani s pravidly projektu v sekci project. Plugin, který přináší pravidla, sekci mít musí, a nesmí to být vyhrazený klíč ani sekce jádra. Uživatel pak píše rozhodnutí pluginu stejně jako kterékoli jiné, vidí je u nálezů, potlačí je komentářem dresscode:ignore acme.debugCalls, přijme jejich rizikové opravy v fixRisky a nechá si je vysvětlit příkazem dresscode explain acme.

Sdílené rozhodnutí. Rozhodnutí obvykle patří jednomu pravidlu, které ho vrací z getDecisions(). Když jednu věc vynucují dvě pravidla s nezávislou logikou, třeba ladicí výstup ve volání a v komentáři, deklaruje rozhodnutí manifest v decisions a obě pravidla ho jmenují v #[RuleInfo(decisions: [...])]. Uživatel píše jeden klíč, který zapne obě pravidla, a nálezy obou se hlásí pod ním:

return new PluginManifest(
	rules: [DebugCallsRule::class, DebugCommentsRule::class],
	section: 'acme',
	decisions: [new Decision('acme.debugOutput', Domain::state('forbidden'), 'Debugging output left in the code')],
);
#[RuleInfo(Stage::Structure, decisions: ['acme.debugOutput'])]
final class DebugCommentsRule extends NodeRule

Pravidlo smí jmenovat i rozhodnutí jádra, třeba file.lineLength, a řídit se jím stejně jako pravidla jádra. Pravidlo, které nějaké rozhodnutí jen čte a nevymáhá, ho jmenuje v reads místo v decisions: rozhodnutí ho nezapne a nálezy se pod ním nehlásí, ale hodnotu pravidlo najde ve svých Values, i když běh pravidla, která ho vymáhají, nepouští. Testy si sdílená rozhodnutí najdou samy: RuleTester přečte plugin z klíče extra.dresscode.plugin v composer.json balíčku, ve kterém pravidlo leží.

Presety z manifestu dostanou svá jména ve tvaru vendor/name, takže je projekt použije jménem v use (acme/house) nebo na jeden běh přepínačem --use. V ruleUrl stojí adresa stránek pravidel se zástupným {slug}, za který se dosadí jméno třídy pravidla bez přípony Rule a s malým prvním písmenem; vypíše ji dresscode explain u každého rozhodnutí pravidla. Vyloučené cesty se přidají k cestám projektu, filtr skipWhen přeskočí soubor, i když má projekt vlastní filtr, a když projekt uvede analýzu stejné třídy jako plugin, platí ta projektová. Plugin, na kterém jiný stojí, leží pod ním a každá třída se použije jen jednou.

Všimněte si, co v manifestu není: plugin nedá žádnému rozhodnutí hodnotu a nenastaví styl, protože takový klíč PluginManifest vůbec nemá; decisions rozhodnutí jen deklaruje. Pravidla pluginu se zapnou, až jejich rozhodnutí napíše projekt nebo preset, který projekt použije. Který styl se použije, je rozhodnutí projektu, a proto ho vždycky přečtete v jeho vlastním souboru:

use:
	- Acme\CodeStyle\Plugin
	- acme/house

acme:
	debugCalls: forbidden

mistakes:
	debugOutputFunctions: [dump]

Pravidla pluginu smějí číst i klíče jádra. acme.debugCalls v ukázce bere seznam ladicích funkcí z parametru jádra mistakes.debugOutputFunctions, takže ho projekt napíše jednou pro všechna pravidla, která se na ladicí funkce ptají; jak to pravidlo udělá, popisuje Pravidlo do detailu.

Přesně takhle pracuje balíček dresscode/upgrade-nette: jeho plugin má sekci nette s rozhodnutími jako nette.linkArguments nebo mapou nette.flagsAsNamedArguments a data pro upgrade knihoven nese vedle v adresáři upgrading/.

Plugin se uvádí v klíči use konfigurace vedle presetů, a to třídou nebo entitou s argumenty. Přepis ani preset ho mít nemohou, protože pravidla a presety zpřístupňuje celému projektu; preset, který by ho uvedl, DressCode odmítne s chybou Preset `acme/house`: Plugin `Acme\CodeStyle\Plugin` is used by the configuration of the project, never by a preset or an override. Filtr skipWhen a továrnu analýzy ale zapíše i samotný dresscode.neon, a to entitou, a továrnu pravidla se závislostmi soubor dresscode.php. Plugin se hodí tam, kde nastavení nese balíček a projekt ho jen zapne.

Balíček pro Composer

Balíček s presetem nebo s pravidly je obyčejný balíček pro Composer, který vyžaduje dresscode/dresscode a sdílí s projektem autoloader. Svůj plugin ohlásí v composer.json pod klíčem extra.dresscode.plugin, takže projekt po instalaci nemusí znát jeho třídu a pustí ho dovnitř jménem balíčku v use:

{
	"name": "acme/code-style",
	"require": {
		"dresscode/dresscode": "^1.0"
	},
	"autoload": {
		"psr-4": {"Acme\\CodeStyle\\": "src/"}
	},
	"extra": {
		"dresscode": {
			"plugin": "Acme\\CodeStyle\\Plugin"
		}
	}
}

Vedle plugin zná DressCode pod extra.dresscode ještě klíče upgrading-dir a packages s daty pro upgrade knihoven; jiný klíč ohlásí jako varování a přeskočí ho, protože balíček může být novější než DressCode, který ho čte. Uvnitř balíčku jsou třídy pravidel, soubory presetů, plugin a fixtury s testy. Jména presetů nesou vendor balíčku (acme/…) a rozhodnutí sekci pluginu, aby se nesrazila s jinými. Uživatel pak balíček nainstaluje, napíše jeho jméno do use a všechno ostatní si vybere v konfiguraci:

use:
	- perCs
	- acme/code-style
	- acme/house

Jméno balíčku žádný preset není, jen pouští dovnitř jeho plugin. Plugin totiž přináší kód, který běží při každé kontrole, a tak se načte, jen když ho projekt jmenuje, stejně jako Composer spouští jen pluginy, které projekt povolí. Nainstalovaný balíček, který use nejmenuje, běh ohlásí varováním Package `acme/code-style` brings the plugin `Acme\CodeStyle\Plugin`, which loads only where `use` names the package or the plugin; skipped. Data pro upgrade pod klíčem upgrading se načtou i bez jména, protože kód nenesou.

Zobrazit na GitHubu · Nahlásit problém se stránkou na GitHubu · 2. 11. 2026