Typy z PHPStanu
Bezztrátový strom zná ve vašem kódu každou mezeru, ale neví, že $form je formulář.
Máte-li v projektu PHPStan, DressCode se ho zeptá, a tím dostane schopnosti, které nástroj na styl kódu nemívá:
přepíše zastaralé API podle toho, čím proměnná opravdu je, doplní #[\Override] k metodě, která přepisuje
rodiče, a pozná, že řetězec je jméno existující třídy.
Co strom sám neví
Pravidlo nad stromem vidí, že na řádku stojí přístup ke konstantě $form::FILLED. Nevidí ale, čím je
$form. Když je to formulář z Nette, jehož třída u konstanty říká
@deprecated use Form::Filled, má se řádek přepsat. Když je to cokoli jiného, nesmí se na něj sáhnout.
Z jednoho souboru se to poznat nedá a oprava, která hádá, je horší než žádná.
Odpověď zná statická analýza a PHPStan ji ve vašem projektu nejspíš už dělá. DressCode proto typy nepočítá sám.
Zeptá se PHPStanu, který máte nainstalovaný, s vaší konfigurací phpstan.neon a s rozšířeními, která
v ní máte. Odpovědi jsou tedy stejně dobré jako analýza, které už dnes věříte, a rozumí i magii vašeho frameworku,
pokud jí rozumí vaše rozšíření PHPStanu.
Zapnutí
PHPStan i DressCode patří do projektu, aby se DressCode ptal právě toho PHPStanu a té konfigurace, kterou projekt používá:
composer require --dev phpstan/phpstan dresscode/dresscode
Počítejte s tím, že DressCode vyžaduje PHP 8.4 nebo novější, takže na něm musí běžet i váš projekt. Pro jakou verzi PHP je kód psaný, je jiné číslo a může zůstat nižší, viz Instalace.
A konfigurace řekne, odkud se typy berou:
types: phpstan
To je všechno. Klíč types je rozhodnutí projektu, stejně jako verze PHP, a preset ho nastavit nemůže. Když
ho uvedete a PHPStan v projektu chybí, DressCode skončí hned při startu:
The configuration sets 'types: phpstan', but phpstan/phpstan is not installed in the project.
Co tím získáte
Takhle vypadá presenter, který používá zastaralé API Nette a několik starších zvyklostí. Zpráva každého nálezu stojí v komentáři na jeho řádku:
final class SignPresenter extends Presenter
{
protected function startup(): void // The method overriding Nette\Application\UI\Presenter::startup() must be marked with #[\Override]
{
parent::startup();
$this->invalidateControl(); // Method Nette\Application\UI\Presenter::invalidateControl() is deprecated: use redrawControl()
}
protected function createComponentSignInForm(): Form
{
$form = new Form;
$form->addText('name')
->addRule($form::FILLED, 'Enter your name'); // Constant Nette\Forms\Form::FILLED is deprecated: use Form::Filled
return $form;
}
public function isAnonymous(string $name): bool
{
return strlen($name) === 0; // The empty string must be tested with === '', not through strlen()
}
public function getRepositoryClass(): string
{
return 'App\Model\UserRepository'; // The class name must be written as App\Model\UserRepository::class, not as a string
}
}
Po opravě:
final class SignPresenter extends Presenter
{
#[\Override]
protected function startup(): void
{
parent::startup();
$this->redrawControl();
}
protected function createComponentSignInForm(): Form
{
$form = new Form;
$form->addText('name')
->addRule($form::Filled, 'Enter your name');
return $form;
}
public function isAnonymous(string $name): bool
{
return $name === '';
}
public function getRepositoryClass(): string
{
return App\Model\UserRepository::class;
}
}
Každý z pěti nálezů potřeboval vědět něco, co v souboru není:
- Zastaralé API přepisuje
dresscode/no-deprecated-memberspodle anotace@deprecatedv kódu knihovny. Rozhoduje přitom třída, která konstantu nebo metodu deklaruje, ne zápis, takže se$form::FILLEDpřepíše stejně jakoForm::FILLED. Jak to funguje u celých knihoven, popisuje Aktualizace knihoven. #[\Override]z PHP 8.3 doplnídresscode/override-attribute-requiredk metodě, která přepisuje metodu rodiče nebo rozhraní. PHP pak samo ohlásí chybu, kdyby rodič metodu přejmenoval a vaše metoda by potichu přestala cokoli přepisovat.- Jméno třídy v řetězci přepíše
dresscode/class-name-reference-for-string-literalna::classjen tehdy, když třída toho jména v projektu opravdu existuje. Editor pak jméno najde, až budete třídu přejmenovávat. Pravidloclass_keywordPHP CS Fixeru podle vlastní dokumentace tohle poznat nemůže, protože nad tokeny neví, jaké třídy projekt má. - Test prázdného řetězce
strlen($name) === 0přepíšedresscode/no-manual-empty-string-testna$name === ''jen tam, kde je$nameurčitě řetězec. U jiné hodnoty by se oba zápisy chovaly jinak.
K tomu dresscode/useless-overriding-method smaže metodu, která jen předá své parametry stejnojmenné metodě
rodiče. Typy prozradí, jestli ji volající od rodičovské vůbec rozezná: rodič ji musí deklarovat se stejnou
viditelností, parametry i návratovým typem. Oprava je riziková (risky fix), protože rodič, který čte
func_get_args(), by pak dostal i argumenty navíc a z výpisu zásobníku zmizí jeden řádek; udělá se, až
pravidlo uvedete v klíči fixRisky.
Zvlášť jedno po druhém tahle pravidla zapínat nemusíte: patří do skupin cleanup, deprecations a
modernization a klíč types je dá do pohybu. Nepleťte si přitom skupinu s klíčem: skupina
říká, co po kódu chcete, klíč říká, odkud se typy kódu zjišťují, a pravidlo, které se bez typů neobejde, není tím
pádem pravidlo skupiny types. Standard dresscode/nette nese cleanup, takže mu stačí
přidat klíč a zbylé dvě skupiny; ty v něm nejsou, protože co která verze zrušila a co nová verze píše líp, je věc
aktualizace, a tu si pustíte, až ji budete chtít:
types: phpstan
presets:
- dresscode/nette
groups:
- deprecations
- modernization
S jiným standardem k němu skupiny přidáte:
types: phpstan
presets:
- dresscode/per
groups:
- cleanup
- deprecations
- modernization
Jak to spolu funguje
DressCode kvůli typům neopouští svůj strom. Vytiskne ho, nechá PHPStan zparsovat vytištěný text a spočítat typy, a každou odpověď připíše ke správnému uzlu podle její pozice v souboru. Pravidla se pak ptají přímo: co je tohle volání zač, kterou metodu ten přístup volá, co o ní říká její deklarace. PHPStan přitom nic nepřepisuje a nic nehlásí, jen odpovídá. Přepisuje dál DressCode, se všemi mezerami a komentáři na svém místě.
Stojí to čas: každý soubor projde dvěma parsery a PHPStan pro něj počítá typy, takže běh s
types: phpstan je znatelně pomalejší než bez nich. Cache výsledků ale platí dál, a tak to pocítíte hlavně
při prvním běhu a po změně konfigurace.
Bez PHPStanu
Bez klíče types DressCode funguje dál, jen pravidla, která typy potřebují, neběží. Když je zapíná
preset nebo skupina, vynechají se potichu, takže dresscode/nette i skupinu types můžete mít
i v projektu bez PHPStanu. dresscode config u nich uvede důvod:
Not running dresscode/override-attribute-required it needs the types of the code and the configuration sets no types
Když ale takové pravidlo zapnete jménem, je to chyba konfigurace. Chtěli jste něco, co běh dát nemůže, a běh bez pravidla by vám lhal, že je kód v pořádku:
Rule dresscode/no-deprecated-members needs the types of the code: set 'types: phpstan' in the configuration, with phpstan/phpstan installed in the project.
Kam dál
- Aktualizace knihoven, kde typy přepisují kód po aktualizaci Nette a dalších knihoven.
- Přechod na novější PHP, pro zbytek přepisů na novější verzi jazyka.
- Konfigurace, kde se klíč
typesskládá s presety a přepisy.