Skip to content

Commit 9b96a56

Browse files
committed
added Nette PHPStan Rules page
1 parent 06a89dd commit 9b96a56

19 files changed

Lines changed: 609 additions & 0 deletions

tools/bg/@home.texy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
----------------
1616
- [Code Checker |code-checker] проверява изходните ви файлове за формални недостатъци като невидим BOM, контролни знаци, повредено кодиране или невалиден синтаксис на PHP, Latte, NEON, JSON и YAML файлове и може да ги поправи автоматично.
1717
- [Coding Standard |en:coding-standard] автоматично форматира вашия PHP код според Nette coding standard, от отстъпите и скобите до интервалите и подреждането на импортите.
18+
- [PHPStan Rules |en:phpstan-rules] учат PHPStan да разбира кода на Nette, така че статичният анализ извежда точни типове и докладва по-малко фалшиви сигнали.
1819

1920

2021
Изкуствен интелект

tools/cs/@home.texy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Kvalita kódu
1515
------------
1616
- [Code Checker |code-checker] zkontroluje vaše zdrojové soubory a najde v nich formální nedostatky, jako je neviditelný BOM, kontrolní znaky, chybné kódování nebo neplatná syntaxe souborů PHP, Latte, NEON, JSON a YAML, a umí je automaticky opravit.
1717
- [Coding Standard |coding-standard] automaticky naformátuje váš PHP kód podle Nette coding standardu, od odsazení a závorek až po mezery a řazení importů.
18+
- [PHPStan Rules |phpstan-rules] naučí PHPStan rozumět Nette kódu, takže statická analýza odvozuje přesné typy a hlásí méně falešných chyb.
1819

1920

2021
Umělá inteligence

tools/cs/phpstan-rules.texy

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
Nette PHPStan Rules
2+
*******************
3+
4+
.[perex]
5+
[PHPStan Rules |https://github.com/nette/phpstan-rules] naučí PHPStan rozumět Nette kódu, takže statická analýza odvozuje přesné typy a hlásí méně falešných chyb.
6+
7+
Stačí rozšíření nainstalovat a [PHPStan |https://phpstan.org] například sám pozná typ komponenty tam, kde dříve viděl chybu:
8+
9+
```php
10+
class HomePresenter extends Presenter
11+
{
12+
protected function createComponentMenu(): MenuControl
13+
{
14+
return new MenuControl;
15+
}
16+
17+
public function renderDefault(): void
18+
{
19+
$menu = $this['menu']; // PHPStan nyní odvodí MenuControl
20+
$menu->setActive('home'); // bez varování o neznámé metodě
21+
}
22+
}
23+
```
24+
25+
26+
Instalace
27+
=========
28+
29+
Rozšíření staví na statickém analyzátoru PHPStan, který odhalí logické chyby v kódu ještě dřív, než jej spustíte. Pokud ho zatím nepoužíváte, nainstalujte ho Composerem:
30+
31+
```shell
32+
composer require --dev phpstan/phpstan
33+
```
34+
35+
Vytvořte konfigurační soubor `phpstan.neon`, kde uvedete adresáře k analýze a úroveň přísnosti:
36+
37+
```neon
38+
parameters:
39+
paths:
40+
- app
41+
42+
level: 8
43+
```
44+
45+
PHPStan se pak spouští příkazem:
46+
47+
```shell
48+
vendor/bin/phpstan analyse
49+
```
50+
51+
Podrobnou dokumentaci najdete na [stránkách PHPStan |https://phpstan.org].
52+
53+
Poté nainstalujte samotné rozšíření:
54+
55+
```shell
56+
composer require --dev nette/phpstan-rules
57+
```
58+
59+
Potřebujete PHP 8.1 nebo vyšší a PHPStan 2.1+.
60+
61+
Aby PHPStan rozšíření používal, je potřeba ho aktivovat. Buď si nainstalujte [phpstan/extension-installer |https://github.com/phpstan/extension-installer], který to zařídí za vás, nebo rozšíření přidejte ručně do `phpstan.neon`:
62+
63+
```neon
64+
includes:
65+
- vendor/nette/phpstan-rules/extension.neon
66+
```
67+
68+
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.
69+
70+
71+
Nativní PHP funkce
72+
==================
73+
74+
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.
75+
76+
Kompletní seznam je v [extension-php.neon |https://github.com/nette/phpstan-rules/blob/master/extension-php.neon].
77+
78+
79+
Closures pro runtime kontrolu typů
80+
----------------------------------
81+
82+
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:
83+
84+
```php
85+
/** @param string[] $items */
86+
public function setItems(array $items): void
87+
{
88+
(function (string ...$items) {})(...$items);
89+
}
90+
```
91+
92+
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.
93+
94+
95+
Aplikace
96+
========
97+
98+
V presenterech metody jako `redirect()`, `forward()` nebo `sendJson()` ukončují běh vyhozením `Nette\Application\AbortException`. Když takové volání obalíte do `try` a zachytíte ho širokým `catch (\Throwable)` nebo `catch (\Exception)`, nechtěně tím přesměrování spolknete. Rozšíření na to upozorní:
99+
100+
```php
101+
try {
102+
$this->redirect('Homepage:');
103+
} catch (\Throwable $e) { // chyba: spolkne AbortException
104+
Debugger::log($e);
105+
}
106+
```
107+
108+
Řešením je výjimku znovu vyhodit, nebo ji vyčlenit do samostatné větve ještě před širokým catchem:
109+
110+
```php
111+
try {
112+
$this->redirect('Homepage:');
113+
} catch (Nette\Application\AbortException $e) {
114+
throw $e;
115+
} catch (\Throwable $e) {
116+
Debugger::log($e);
117+
}
118+
```
119+
120+
121+
Assety
122+
======
123+
124+
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:
125+
126+
```neon
127+
parameters:
128+
nette:
129+
assets:
130+
mapping:
131+
default: file # Nette\Assets\FilesystemMapper
132+
images: file
133+
vite: vite # Nette\Assets\ViteMapper
134+
custom: App\MyMapper # libovolné FQCN
135+
```
136+
137+
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.
138+
139+
Po nastavení:
140+
141+
- `Registry::getMapper('vite')` vrací `ViteMapper` místo `Mapper`.
142+
- `Registry::getAsset('default:logo.png')` vrací `ImageAsset`. `tryGetAsset()` vrací `ImageAsset|null`.
143+
- `FilesystemMapper::getAsset('button.js')` a `ViteMapper::getAsset()` se zúžují stejným způsobem.
144+
145+
146+
Component Model
147+
===============
148+
149+
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ě.
150+
151+
```php
152+
class HomePresenter extends Presenter
153+
{
154+
protected function createComponentMenu(): MenuControl
155+
{
156+
return new MenuControl;
157+
}
158+
159+
public function renderDefault(): void
160+
{
161+
$menu = $this->getComponent('menu'); // MenuControl
162+
$menu = $this['menu']; // MenuControl
163+
}
164+
}
165+
```
166+
167+
Pokud odpovídající factory neexistuje nebo název komponenty není konstantní string, zůstane návratový typ `getComponent()` i `$this['name']` nezměněný, tedy obecný `IComponent`.
168+
169+
170+
Dependency Injection
171+
====================
172+
173+
Vlastnosti označené atributem `#[Nette\DI\Attributes\Inject]` plní dependency injection až po vytvoření objektu. PHPStan by je proto hlásil jako neinicializované; rozšíření je naopak bere jako zapsané a inicializované:
174+
175+
```php
176+
class HomePresenter extends Presenter
177+
{
178+
#[Inject]
179+
public CartFacade $cart; // bez chyby o neinicializované vlastnosti
180+
}
181+
```
182+
183+
184+
Formuláře
185+
=========
186+
187+
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()`:
188+
189+
```php
190+
public function createComponentSignInForm(): Form
191+
{
192+
$form = new Form;
193+
$form->addText('username', 'Username');
194+
$form->addPassword('password', 'Password');
195+
196+
$form['username']; // TextInput
197+
$form['password']; // TextInput (Password je potomek)
198+
return $form;
199+
}
200+
```
201+
202+
Přístup funguje i z jiné metody, než ve které formulář vznikl. Když ho vytvoříte ve factory `createComponentSignInForm()` a k jeho prvkům přistupujete jinde, rozšíření si přiřazení vystopuje zpět až k factory a dohledá odpovídající volání `addXxx()`:
203+
204+
```php
205+
public function renderDefault(): void
206+
{
207+
$form = $this['signInForm']; // dohledá se createComponentSignInForm()
208+
$form['username']; // TextInput
209+
210+
// stejně tak funguje i přímý přístup
211+
$this['signInForm']['username']; // TextInput
212+
$this['signInForm-username']; // TextInput
213+
}
214+
```
215+
216+
Pokud žádné odpovídající volání `addXxx()` neexistuje, rozšíření se stejně jako Component Model pokusí najít factory `createComponent<Name>()`.
217+
218+
219+
Vlastnosti event handlerů
220+
-------------------------
221+
222+
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:
223+
224+
```php
225+
$form->onSuccess[] = function (Form $form, MyDto $data): void {
226+
// …
227+
};
228+
```
229+
230+
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`.
231+
232+
233+
Schema
234+
======
235+
236+
Rozšíření zúží návratový typ `Expect::array()` z deklarovaného unionu `Structure|Type` podle předaného argumentu:
237+
238+
```php
239+
Expect::array(); // Type
240+
Expect::array(['name' => Expect::string()]); // Structure (všechny hodnoty jsou Schema)
241+
Expect::array(['name' => Expect::string(), 'x']); // Structure|Type (Schema i ne-Schema hodnoty)
242+
```
243+
244+
Pokud argument obsahuje Schema i ne-Schema hodnoty, deklarovaný union zůstane zachován.
245+
246+
247+
Tester
248+
======
249+
250+
PHPStan po voláních metod `Tester\Assert` zúží typ proměnné. Podporované metody: `null()`, `notNull()`, `true()`, `false()`, `truthy()`, `falsey()`, `same()`, `notSame()`, `type()`.
251+
252+
```php
253+
function process(?User $user): void
254+
{
255+
Assert::notNull($user);
256+
$user->getName(); // bez varování o volání na null
257+
}
258+
```
259+
260+
261+
Arrow funkce jako void callbacky
262+
--------------------------------
263+
264+
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()`.
265+
266+
267+
Utils
268+
=====
269+
270+
**`Strings::match()` a `matchAll()`**: u konstantního patternu se návratový typ odvodí přímo z regulárního výrazu, tedy z jeho zachytávajících skupin (včetně pojmenovaných a volitelných). Flagy `captureOffset`, `unmatchedAsNull` a u `matchAll()` i `patternOrder` a `lazy` se promítnou do výsledného tvaru:
271+
272+
```php
273+
Strings::match($s, '#(\d+)-(\w+)#'); // array{non-falsy-string, decimal-int-string, non-empty-string}|null
274+
Strings::match($s, '#(?<id>\d+)#'); // array{0: non-empty-string, id: decimal-int-string, 1: decimal-int-string}|null
275+
Strings::matchAll($s, '#(\w+)#'); // list<array{string, non-empty-string}>
276+
```
277+
278+
U nekonstantního patternu (a u metody `split()`) se tvar odvodí jen z flagů.
279+
280+
**`Strings::replace()`**: je-li náhrada callback, typ jeho parametru `$matches` se odvodí ze stejného regulárního výrazu:
281+
282+
```php
283+
Strings::replace($s, '#(\d+)#', function (array $m) {
284+
return $m[1]; // $m má typ array{non-empty-string, decimal-int-string}
285+
});
286+
```
287+
288+
**Zúžení subjektu po `match()`**: uvnitř `if (Strings::match($s, …))` se podle patternu zúží i typ prohledávaného řetězce `$s`, například na `non-empty-string`.
289+
290+
**Validace patternů**: nevalidní regulární výraz předaný do `match()`, `matchAll()`, `split()` nebo `replace()` rozšíření nahlásí už při analýze, ne až za běhu.
291+
292+
**`Arrays::invoke()`** a **`Arrays::invokeMethod()`** vracejí místo deklarovaného `array` pole s typem návratové hodnoty volaného callable, resp. metody.
293+
294+
**`Helpers::falseToNull()`** zúží návratový typ tak, že odstraní `false` a přidá `null`. Z `string|false` se tedy stane `string|null`.
295+
296+
**`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`.

tools/de/@home.texy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Code-Qualität
1515
-------------
1616
- [Code Checker |code-checker] überprüft Ihre Quelldateien auf formale Mängel wie ein unsichtbares BOM, Steuerzeichen, fehlerhafte Kodierung oder ungültige Syntax von PHP-, Latte-, NEON-, JSON- und YAML-Dateien und kann diese automatisch beheben.
1717
- [Coding Standard |en:coding-standard] formatiert Ihren PHP-Code automatisch gemäß dem Nette Coding Standard, von der Einrückung und den Klammern bis hin zu Abständen und der Reihenfolge der Imports.
18+
- [PHPStan Rules |en:phpstan-rules] bringen PHPStan bei, Nette-Code zu verstehen, sodass die statische Analyse präzise Typen ableitet und weniger Fehlalarme meldet.
1819

1920

2021
Künstliche Intelligenz

tools/el/@home.texy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
---------------
1616
- [Code Checker |code-checker] ελέγχει τα πηγαία αρχεία σας για τυπικά σφάλματα, όπως ένα αόρατο BOM, χαρακτήρες ελέγχου, κατεστραμμένη κωδικοποίηση ή μη έγκυρη σύνταξη αρχείων PHP, Latte, NEON, JSON και YAML, και μπορεί να τα διορθώσει αυτόματα.
1717
- [Coding Standard |en:coding-standard] μορφοποιεί αυτόματα τον κώδικα PHP σας σύμφωνα με το πρότυπο κωδικοποίησης του Nette, από τη στοίχιση και τα άγκιστρα μέχρι τα κενά και τη σειρά των imports.
18+
- [PHPStan Rules |en:phpstan-rules] μαθαίνουν στο PHPStan να κατανοεί τον κώδικα Nette, ώστε η στατική ανάλυση να συμπεραίνει ακριβείς τύπους και να αναφέρει λιγότερα ψευδώς θετικά.
1819

1920

2021
Τεχνητή νοημοσύνη

tools/en/@home.texy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Code Quality
1515
------------
1616
- [Code Checker |code-checker] checks your source files for formal flaws such as an invisible BOM, control characters, broken encoding, or invalid syntax of PHP, Latte, NEON, JSON and YAML files, and can fix them automatically.
1717
- [Coding Standard |coding-standard] automatically formats your PHP code according to the Nette coding standard, from indentation and braces to spacing and the ordering of imports.
18+
- [PHPStan Rules |phpstan-rules] teach PHPStan to understand Nette code, so static analysis infers precise types and reports fewer false positives.
1819

1920

2021
Artificial Intelligence

0 commit comments

Comments
 (0)