Skip to content

Commit 1542e52

Browse files
committed
added Nette PHPStan Rules page
1 parent 53853e3 commit 1542e52

6 files changed

Lines changed: 414 additions & 0 deletions

File tree

best-practices/cs/@home.texy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Obecné
4444
- [Proč Nette nepoužívá příponu Interface? |https://blog.nette.org/cs/predpony-a-pripony-do-nazvu-rozhrani-nepatri]
4545
- [Composer: tipy pro použití |composer]
4646
- [Tipy na editory & nástroje |editors-and-tools]
47+
- [Nette PHPStan Rules |phpstan-rules]
4748
- [Úvod do objektově orientovaného programování |nette:introduction-to-object-oriented-programming]
4849

4950
</div>

best-practices/cs/editors-and-tools.texy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ vendor/bin/phpstan analyse app
5555

5656
Vyčerpávající dokumentaci najdete přímo na [stránkách PHPStan |https://phpstan.org].
5757

58+
Aby byl PHPStan na Nette kódu ještě chytřejší, nainstalujte si také [Nette PHPStan Rules |phpstan-rules]. Přidá přesnější návratové typy Nette helperů, zúží typy komponent a formulářových prvků, odstraní nemožné `|false`/`|null` z mnoha nativních PHP funkcí a ztiší známá falešná hlášení specifická pro Nette.
59+
5860

5961
Code Checker
6062
============
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
Nette PHPStan Rules
2+
*******************
3+
4+
.[perex]
5+
Rozšíření `nette/phpstan-rules` naučí [PHPStan |https://phpstan.org] lépe rozumět Nette kódu. Stačí ho nainstalovat a PHPStan začne odvozovat přesné typy tam, kde dříve znal jen obecné. Například:
6+
7+
```php
8+
class HomePresenter extends Presenter
9+
{
10+
protected function createComponentMenu(): MenuControl
11+
{
12+
return new MenuControl;
13+
}
14+
15+
public function renderDefault(): void
16+
{
17+
$menu = $this['menu']; // PHPStan nyní odvodí MenuControl
18+
$menu->setActive('home'); // bez varování o "neznámé metodě na Component"
19+
}
20+
}
21+
```
22+
23+
24+
Instalace
25+
=========
26+
27+
Nainstalujte přes Composer:
28+
29+
```shell
30+
composer require --dev nette/phpstan-rules
31+
```
32+
33+
Potřebujete PHP 8.1 nebo vyšší a PHPStan 2.1+.
34+
35+
Pokud používáte [phpstan/extension-installer |https://github.com/phpstan/extension-installer], rozšíření se zaregistruje automaticky. Jinak ho přidejte do `phpstan.neon`:
36+
37+
```neon
38+
includes:
39+
- vendor/nette/phpstan-rules/extension.neon
40+
```
41+
42+
Většina kontrol funguje bez další konfigurace. Pouze pro funkce popsané v sekci `Assety` je potřeba malý konfigurační blok v `phpstan.neon` (viz níže). Veškerá konfigurace uvedená na této stránce patří do `phpstan.neon`, nikoli do `common.neon` nebo jiných konfiguračních souborů Nette DI.
43+
44+
45+
Nativní PHP funkce
46+
==================
47+
48+
Mnoho nativních PHP funkcí má v deklarovaném návratovém typu `string|false` nebo `array|null`, ačkoli se chybová hodnota objevuje jen za podmínek, které v moderním kódu prakticky nemohou nastat: selhání `getcwd()` na funkčním filesystému, selhání `json_encode()` bez `JSON_THROW_ON_ERROR`, selhání `preg_split()` na konstantním patternu a podobně. Rozšíření tyto chybové hodnoty z návratových typů odstraní, takže PHPStan přestane vyžadovat ošetření chyb, které *nemohou* nastat.
49+
50+
Kompletní seznam je v [extension-php.neon |https://github.com/nette/phpstan-rules/blob/master/extension-php.neon].
51+
52+
53+
Closures pro runtime kontrolu typů
54+
----------------------------------
55+
56+
Běžný PHP idiom pro runtime ověření, že pole obsahuje hodnoty deklarovaného typu, používá typovanou variadickou closure volanou s operátorem spread:
57+
58+
```php
59+
/** @param string[] $items */
60+
public function setItems(array $items): void
61+
{
62+
(function (string ...$items) {})(...$items);
63+
}
64+
```
65+
66+
PHP vynutí typ `string` na každém argumentu a vyhodí `TypeError`, pokud některý prvek není string. Tělo closure je prázdné a výraz existuje pouze kvůli vedlejšímu efektu. PHPStan by jinak hlásil `expr.resultUnused`, toto pravidlo ale daný vzor rozpozná a chybu nevypíše.
67+
68+
69+
Assety
70+
======
71+
72+
V `phpstan.neon` (nikoli v konfiguraci Nette DI) nastavte mapování ID mapperů na třídy, aby PHPStan dokázal zúžit obecný typ `Asset` na konkrétní třídu:
73+
74+
```neon
75+
parameters:
76+
nette:
77+
assets:
78+
mapping:
79+
default: file # Nette\Assets\FilesystemMapper
80+
images: file
81+
vite: vite # Nette\Assets\ViteMapper
82+
custom: App\MyMapper # libovolné FQCN
83+
```
84+
85+
Hodnoty `file` a `vite` jsou zkratky pro vestavěné `FilesystemMapper` a `ViteMapper`. Jakákoli jiná hodnota se považuje za plně kvalifikovaný název vlastní třídy mapperu.
86+
87+
Po nastavení:
88+
89+
- `Registry::getMapper('vite')` vrací `ViteMapper` místo `Mapper`.
90+
- `Registry::getAsset('default:logo.png')` vrací `ImageAsset`. `tryGetAsset()` vrací `ImageAsset|null`.
91+
- `FilesystemMapper::getAsset('button.js')` a `ViteMapper::getAsset()` se zúžují stejným způsobem.
92+
93+
94+
Component Model
95+
===============
96+
97+
Rozšíření zúží návratový typ `Container::getComponent()` a `Container::offsetGet()` (tedy `$this['name']`) podle factory metod `createComponent<Name>()` deklarovaných na téže třídě.
98+
99+
```php
100+
class HomePresenter extends Presenter
101+
{
102+
protected function createComponentMenu(): MenuControl
103+
{
104+
return new MenuControl;
105+
}
106+
107+
public function renderDefault(): void
108+
{
109+
$menu = $this->getComponent('menu'); // MenuControl
110+
$menu = $this['menu']; // MenuControl
111+
}
112+
}
113+
```
114+
115+
Pokud odpovídající factory neexistuje nebo název komponenty není konstantní string, ponechá se deklarovaný návratový typ.
116+
117+
118+
Formuláře
119+
=========
120+
121+
Pokud je volání `$form->addText('name', …)`, `$form->addSelect(…)` apod. ve stejné funkci nebo metodě jako přístup k `$form['name']` (případně `$form->getComponent('name')`), rozšíření odvodí typ přístupu z odpovídajícího volání `addXxx()`:
122+
123+
```php
124+
public function createComponentSignInForm(): Form
125+
{
126+
$form = new Form;
127+
$form->addText('username', 'Username');
128+
$form->addPassword('password', 'Password');
129+
130+
$form['username']; // TextInput
131+
$form['password']; // TextInput (Password je potomek)
132+
return $form;
133+
}
134+
```
135+
136+
Pokud žádné odpovídající volání `addXxx()` neexistuje, rozšíření se stejně jako Component Model pokusí najít factory `createComponent<Name>()`.
137+
138+
139+
Vlastnosti event handlerů
140+
-------------------------
141+
142+
Formuláře data převedou na typ deklarovaný v parametru callbacku, ať jde o `stdClass`, `array` nebo vlastní DTO. Callback s užším datovým parametrem, než je deklarovaný union `array|object`, je proto v pořádku:
143+
144+
```php
145+
$form->onSuccess[] = function (Form $form, MyDto $data): void {
146+
// …
147+
};
148+
```
149+
150+
PHPStan by jinak hlásil `assign.propertyType`, protože `MyDto` je užší než `array|object`. Pravidlo tuto chybu potlačuje u vlastností `Form::$onSuccess`, `$onError`, `$onSubmit`, `$onRender`, `Container::$onValidate`, `SubmitButton::$onClick` a `$onInvalidClick`.
151+
152+
153+
Schema
154+
======
155+
156+
Rozšíření zúží návratový typ `Expect::array()` z deklarovaného unionu `Structure|Type` podle předaného argumentu:
157+
158+
```php
159+
Expect::array(); // Type
160+
Expect::array(['name' => Expect::string()]); // Structure (všechny hodnoty jsou Schema)
161+
Expect::array(['name' => Expect::string(), 'x']); // Structure|Type (Schema i ne-Schema hodnoty)
162+
```
163+
164+
Pokud argument obsahuje Schema i ne-Schema hodnoty, deklarovaný union zůstane zachován.
165+
166+
167+
Tester
168+
======
169+
170+
PHPStan po voláních metod `Tester\Assert` zúží typ proměnné. Podporované metody: `null`, `notNull`, `true`, `false`, `truthy`, `falsey`, `same`, `notSame`, `type`.
171+
172+
```php
173+
function process(?User $user): void
174+
{
175+
Assert::notNull($user);
176+
$user->getName(); // bez varování o volání na null
177+
}
178+
```
179+
180+
Arrow funkce jako void callbacky
181+
--------------------------------
182+
183+
Funkce Testeru `test()` a `Assert::exception()` přijímají callbacky typované jako `Closure(): void`, ale často se jim předávají arrow funkce ve stylu `fn () => throw new MyException`. Arrow funkce vždy vrací nějakou hodnotu, což by PHPStan jinak označil za typovou neshodu. Pravidlo tuto chybu potlačuje u následujících funkcí a metod: `test`, `testException`, `testNoError`, `Tester\Assert::exception`, `Tester\Assert::throws`, `Tester\Assert::error`, `Tester\Assert::noError`.
184+
185+
186+
Utils
187+
=====
188+
189+
**`Strings::match()`, `matchAll()`, `split()`**: návratové typy se odvodí z booleovských flagů (`captureOffset`, `unmatchedAsNull`, `patternOrder`, `lazy`):
190+
191+
```php
192+
Strings::match($s, '#(\w+)#'); // array<string>|null
193+
Strings::match($s, '#(\w+)#', captureOffset: true); // array<array{string, int<0, max>}>|null
194+
Strings::match($s, '#(\w+)#', unmatchedAsNull: true); // array<string|null>|null
195+
Strings::matchAll($s, '#(\w+)#', lazy: true); // Generator<int, array<string>>
196+
```
197+
198+
Pokud flag není konstantní hodnota, zachová se deklarovaný návratový typ.
199+
200+
**`Arrays::invoke()`** a **`Arrays::invokeMethod()`** vracejí místo deklarovaného `array` pole s typem návratové hodnoty volaného callable, resp. metody.
201+
202+
**`Helpers::falseToNull()`** zúží návratový typ tak, že odstraní `false` a přidá `null`. Z `string|false` se tedy stane `string|null`.
203+
204+
**`Html` magické metody**: `$el->setClass(…)`, `$el->addData(…)`, `$el->getHref()` a podobné se rozpoznají i bez `@method` anotací. `setXxx()` a `addXxx()` vrací `static` (fluent API), `getXxx()` vrací `mixed`.

best-practices/en/@home.texy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ General
4444
- [Why Doesn't Nette Use the Interface Suffix? |https://blog.nette.org/en/prefixes-and-suffixes-do-not-belong-in-interface-names]
4545
- [Composer: Usage Tips |composer]
4646
- [Tips for Editors & Tools |editors-and-tools]
47+
- [Nette PHPStan Rules |phpstan-rules]
4748
- [Introduction to Object-Oriented Programming |nette:introduction-to-object-oriented-programming]
4849

4950
</div>

best-practices/en/editors-and-tools.texy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ vendor/bin/phpstan analyse app
5555

5656
You can find comprehensive documentation directly on the [PHPStan website |https://phpstan.org].
5757

58+
To make PHPStan even smarter about Nette code, install [Nette PHPStan Rules |phpstan-rules] as well. It adds precise return types for Nette helpers, narrows component and form types, removes impossible `|false`/`|null` from many native PHP functions, and silences known Nette-specific false positives.
59+
5860

5961
Code Checker
6062
============

0 commit comments

Comments
 (0)