From 57fdc840952051eb76b62e867f60915ffa97678c Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 8 Jan 2026 03:31:42 +0100 Subject: [PATCH 01/92] added ai.nette.org --- ai/cs/@home.texy | 97 +++++++++ ai/cs/@left-menu.texy | 5 + ai/cs/@meta.texy | 1 + ai/cs/guide.texy | 1 + ai/cs/mcp-inspector.texy | 326 +++++++++++++++++++++++++++++ ai/cs/tips.texy | 1 + ai/cs/vibe-coding.texy | 298 ++++++++++++++++++++++++++ ai/en/@home.texy | 97 +++++++++ ai/en/@left-menu.texy | 5 + ai/en/@meta.texy | 1 + ai/en/agent-plugins.texy | 416 +++++++++++++++++++++++++++++++++++++ ai/en/getting-started.texy | 260 +++++++++++++++++++++++ ai/en/guide.texy | 1 + ai/en/mcp-inspector.texy | 325 +++++++++++++++++++++++++++++ ai/en/tips.texy | 1 + ai/en/vibe-coding.texy | 298 ++++++++++++++++++++++++++ ai/meta.json | 1 + 17 files changed, 2134 insertions(+) create mode 100644 ai/cs/@home.texy create mode 100644 ai/cs/@left-menu.texy create mode 100644 ai/cs/@meta.texy create mode 100644 ai/cs/guide.texy create mode 100644 ai/cs/mcp-inspector.texy create mode 100644 ai/cs/tips.texy create mode 100644 ai/cs/vibe-coding.texy create mode 100644 ai/en/@home.texy create mode 100644 ai/en/@left-menu.texy create mode 100644 ai/en/@meta.texy create mode 100644 ai/en/agent-plugins.texy create mode 100644 ai/en/getting-started.texy create mode 100644 ai/en/guide.texy create mode 100644 ai/en/mcp-inspector.texy create mode 100644 ai/en/tips.texy create mode 100644 ai/en/vibe-coding.texy create mode 100644 ai/meta.json diff --git a/ai/cs/@home.texy b/ai/cs/@home.texy new file mode 100644 index 0000000000..1b467d225d --- /dev/null +++ b/ai/cs/@home.texy @@ -0,0 +1,97 @@ +Vibe coding s Nette +******************* + +
+ +AI agent vám za pár sekund napíše sto řádků PHP. Jestli budou opravdu fungovat, o tom rozhoduje něco, co si agent sám obstarat nedokáže: základ, který si umí přečíst a ověřit a na kterém může stavět. + +Nette ho buduje léta, už v době, kdy to vypadalo jako detail pro fajnšmekry. + +- **Otypované do morku kostí** - agent typy čte, místo aby je hádal +- **Jasné konvence** - ví, kam která třída patří +- **Uzavřená smyčka** - vidí vaši skutečnou aplikaci a kontroluje si vlastní práci + +
+ + +IDE bylo pro člověka, analýza je pro stroj +========================================== + +Roky jsme nástroje milovali za našeptávač, za typ, který vyskočí při najetí myší, za skok na definici. To všechno je ale pomoc pro lidské oko a ruku. Agent obrazovku nečte. Potřebuje informaci, kterou si přečte jako text a hlavně si ji umí ověřit sám. + +V tom je celý rozdíl mezi IDE a statickou analýzou. PhpStorm vám decentně podtrhne místo, kde možná předáváte `null`. PHPStan tutéž věc napíše do konzole jako chybu, kterou agent přečte, pochopí a opraví, aniž byste u toho museli sedět. + +Otázka proto už nezní "jak hezky mi editor popíše tohle API?", ale "kolik toho o sobě říká sám kód?" + + +Nette toho říká hodně +===================== + +Když PHP 7.1 doplnilo typový systém o nullable typy, bylo Nette vůbec prvním full-stack frameworkem, který si otypoval celé své API, včetně `declare(strict_types=1)` v každém souboru. Jakmile PHP 7.4 přidalo typované property, Nette je převzalo. Co uměl jazyk vyjádřit nativně, to Nette používalo, většinou dřív než ostatní. + +A tam, kde nativní typy nestačí, nastupují anotace. PHP neumí zapsat "neprázdný seznam řetězců" ani "návratová hodnota závisí na argumentu". Nette obojí zapíše, přesně tam, kde na tom záleží: + +```php +$data = $form->getValues(RegistrationData::class); +echo $data->email; // $data je RegistrationData, plně otypované +``` + +Žádný nativní typ PHP tohle nezvládne: "když ti předám jméno třídy, vrátíš mi instanci té třídy". Podmíněný návratový typ to zvládne a přečte si ho statická analýza, vaše IDE i agent. Napíšete `$data->emial` a chybu máte ještě před spuštěním. + +Stejná péče prostupuje celým frameworkem: array shapes, `class-string`, `positive-int`, `non-empty-string`. **Všechny balíčky Nette procházejí PHPStanem na plný level 8**, ne jen části, ne s hromadou výjimek. Stavíte na základu otypovaném tak důsledně, jak to jen jde. A na dobře otypovaném základu se snadno staví dobře otypovaná aplikace. + +.[note] +Celý příběh najdete v článku [AI agent je tak dobrý, jak dobré jsou vaše typy |https://blog.nette.org/cs/agenti-a-typova-kontrola]. + + +Celé je to o smyčce +=================== + +Agent je přesně tak dobrý, jak dobrá je zpětná vazba, kterou dostává. A ta nejlepší je smyčka. Něco napíše, spustí statickou analýzu a testy, přečte si výsledek, opraví chybu a zkusí to znovu. Napiš, ověř, oprav, opakuj. Tomuhle cyklu se říká agentní smyčka a je to okamžik, kdy se z generátoru textu stane něco, co kód opravdu doladí do funkčního stavu. + +Nette mu dá do ruky všechno, co k tomu potřebuje: + +- **PHPStan** s [rozšířením pro Nette |tools:phpstan-rules], které analyzátor naučí specifika frameworku: ví, že `$this['menu']` je vaše `MenuControl`, odvodí typ prvku z `addText()`, rozumí `#[Inject]` +- **[Nette Tester |tester:]** na testy. Zatím žádné nemáte? Nechte si je od agenta vygenerovat jako úplně první věc, protože bez nich nemá jak poznat, že něco rozbil. +- **Automatický lint** PHP, Latte, NEON, JSON a JavaScriptu po každé jednotlivé úpravě, takže rozbitá šablona se nikdy nedostane až do prohlížeče + + +Vaše aplikace, ne odhad +======================= + +Požádejte libovolnou AI, ať "vygeneruje entitu pro tabulku product", a bude vaše sloupce hádat podle vzorů, které pochytila při trénování. Jenže sloupec s cenou se u vás jmenuje `unit_price` a je tam `currency_id`, o kterém jste se nezmínili. + +**[MCP Inspector |mcp-inspector]** hádání odstraní. Je to server, díky kterému se agent podívá přímo do vaší běžící aplikace: na služby v DI kontejneru, na skutečné schéma databáze, na routy, na logy Tracy. Všechno je výhradně pro čtení a funguje to v každém editoru, který podporuje Model Context Protocol: Claude Code, Cursor, VS Code a další. + +Mění se i ladění. Místo kopírování stack trace do chatu řeknete "koukni do logu a zjisti, co se rozbilo", a agent si výjimku přečte sám. + + +Agent, který zná Nette +====================== + +**[Plugin pro Claude Code |agent-plugins]** dodá to, co ve vašem kódu nikde nenajde: jak se věci v Nette dělají. Celá rodina skills pokrývá architekturu, formuláře, databázi, komponenty, bezpečnost, Latte, NEON a další. Každý se zapne sám, jakmile na jeho téma přijde řeč. Zeptáte se na validaci formuláře a naskočí skill pro formuláře. Zeptáte se na BlueScreen a naskočí ten pro Tracy. + +Výsledek vypadá, jako by ho psal někdo, kdo Nette dělá roky, ne jako obecné PHP poslepované z útržků. + + +Jak začít +========= + +Celé nastavení zabere zhruba deset minut: + +1. [Nainstalujte si AI nástroj |getting-started], doporučujeme Claude Code +2. [Přidejte plugin pro Nette |agent-plugins], aby agent znal framework +3. [Nastavte MCP Inspector |mcp-inspector], aby znal *vaši* aplikaci + +Pak už jen popište, co chcete, zavřete agenta do smyčky a nechte ho pracovat. Až budete chtít z agenta vytěžit víc, přečtěte si [Vibe coding v praxi |vibe-coding]. + + +Když má s AI pracovat i vaše aplikace +===================================== + +Celá tahle stránka je o AI, která pomáhá psát kód. Uvnitř vaší aplikace ale může AI dělat docela jinou práci: shrnout článek, roztřídit příchozí dotazy, vytáhnout z faktury údaje do databáze. + +Na to je [AI Access |https://ai-access.deegee.dev], PHP knihovna s jedním rozhraním pro OpenAI, Claude, Gemini, DeepSeek i Grok. Přepnutí mezi nimi je změna jediného řádku. A protože nemá žádné závislosti, nepřitáhne vám do projektu SDK od výrobce ani konflikty verzí. + +{{maintitle: Vibe coding s Nette – PHP framework, kterému AI agent rozumí}} +{{description: AI agenti hádají přesně tolik, kolik jim váš kód dovolí. Nette je otypované do morku kostí, prochází PHPStanem na level 8 a dává agentovi ověřitelnou smyčku.}} diff --git a/ai/cs/@left-menu.texy b/ai/cs/@left-menu.texy new file mode 100644 index 0000000000..5276d2757e --- /dev/null +++ b/ai/cs/@left-menu.texy @@ -0,0 +1,5 @@ +- [Úvod |@home] +- [Začínáme |getting-started] +- [Plugin pro Claude Code |agent-plugins] +- [MCP Inspector |mcp-inspector] +- [Vibe coding v praxi |vibe-coding] diff --git a/ai/cs/@meta.texy b/ai/cs/@meta.texy new file mode 100644 index 0000000000..e06cc9886c --- /dev/null +++ b/ai/cs/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette AI}} diff --git a/ai/cs/guide.texy b/ai/cs/guide.texy new file mode 100644 index 0000000000..3ec981a90e --- /dev/null +++ b/ai/cs/guide.texy @@ -0,0 +1 @@ +{{redirect: @home}} diff --git a/ai/cs/mcp-inspector.texy b/ai/cs/mcp-inspector.texy new file mode 100644 index 0000000000..355d9dca67 --- /dev/null +++ b/ai/cs/mcp-inspector.texy @@ -0,0 +1,326 @@ +MCP Inspector +************* + +
+ +"MCP Inspector":https://github.com/nette/mcp-inspector umožní AI asistentovi podívat se přímo do vaší Nette aplikace: vidí, jaké služby máte registrované v DI kontejneru, jak vypadají tabulky v databázi, která routa vede kam a co Tracy zalogovala v noci. Dozvíte se: + +- jak inspektor funguje a co všechno vidí +- jak ho nainstalovat dvěma příkazy +- co dělá který nástroj a jak si ho vyzkoušet z terminálu +- jak držet AI na krátkém vodítku: dotazy jen pro čtení, maskovaná hesla, vypínač + +
+ +Bez inspektoru AI vaši aplikaci odhaduje podle vzorů, které pochytila při tréninku. S ním se AI zeptá vaší aplikace a dostane pravdu: skutečné sloupce, skutečné názvy služeb, skutečnou chybu. + +.[caution] +MCP Inspector je stále v **rané fázi vývoje a nemá zatím stabilní vydání**. Do prvního vydání ho instalujte příkazem `composer require --dev nette/mcp-inspector:@dev` a počítejte s tím, že se názvy nástrojů i konfigurace budou ještě měnit. + + +Jak to funguje +============== + +MCP Inspector je server mluvící **Model Context Protocolem (MCP)**, standardem, kterým AI nástroje jako Claude Code, Cursor nebo VS Code volají externí nástroje. Editor spustí inspektor jako proces na pozadí, a kdykoli AI potřebuje něco z vaší aplikace, zavolá některý z nástrojů inspektoru a dostane odpověď. + +Aby mohl odpovědět, sestaví inspektor DI kontejner vaší aplikace. Dělá to pomocí malého skriptu `mcp-bootstrap.php` v kořeni projektu, který vrací `Configurator` vaší aplikace se všemi přidanými konfigy; kontejner si inspektor vytvoří sám, v debug režimu a ve vlastním temp adresáři, takže se nikdy nedotkne cache vašeho webu. + +Kontejner zůstává mezi voláními živý, ale každé volání zkontroluje, zda se nezměnila konfigurace. Když upravíte `services.neon`, hned další volání nástroje vidí nové služby; restart editoru není potřeba. Pokud se přestavba nepovede, třeba kvůli překlepu v konfiguraci, inspektor dál obsluhuje poslední funkční kontejner a do výsledku přidá pole `_warning`, takže vám AI o selhání hned řekne. + +Vše je **ve výchozím stavu jen pro čtení**: inspektor čte vaše služby, schéma, routy a logy, ale nemůže měnit data ani konfiguraci a nikdy nespouští kód od AI. Jediná výjimka, spouštění modifikujícího SQL, je vypnutá, dokud ji výslovně [nepovolíte |#konfigurace-databaze]. + + +Instalace +========= + +Dva příkazy. První přidá balíček jako vývojovou závislost, druhý vygeneruje soubory, které inspektor potřebuje: + +```shell +composer require --dev nette/mcp-inspector:@dev +vendor/bin/mcp-inspector init +``` + +`init` vytvoří tři soubory a existující nikdy nepřepíše: + +| Soubor | Účel +|------|------ +| `mcp-bootstrap.php` | vrací `Nette\Bootstrap\Configurator` vaší aplikace (viz níže) +| `config/mcp-inspector.neon` | konfigurace inspektoru: co AI smí +| `.mcp.json` | registruje server `nette-inspector` pro Claude Code; stejný záznam dostanou `.cursor/mcp.json` a `.vscode/mcp.json`, pokud tyto adresáře existují + +Pak restartujte svůj AI nástroj (v Claude Code napište `/exit` a spusťte znovu `claude`): MCP servery se připojují při startu nástroje. + +Hodí se dvě volby. Když PHP neběží přímo na vašem počítači, předejte příkaz, který má AI nástroj použít: `--php="ddev exec php"`. Když projekt není aktuálním adresářem, přidejte `--project=CESTA`. + +Funguje to? Zeptejte se AI: + +``` +Jaké služby mám registrované v DI kontejneru? +``` + +Pokud odpověď vypíše skutečné služby vaší aplikace, máte hotovo. + + +Soubor mcp-bootstrap.php +======================== + +Inspektor potřebuje `Configurator` se všemi přidanými konfigy, ale *před* zavoláním `createContainer()`, protože kontejner si sestavuje sám. `init` se podívá na vaši třídu `App\Bootstrap` a soubor vygeneruje podle ní: + +- **Statická `App\Bootstrap::boot(): Configurator`** (klasický Web Project): soubor je prostě `return App\Bootstrap::boot();` +- **Objektový `Bootstrap` s `bootWebApplication(): Container`** (Web Project od roku 2024): přidejte metodu, která se zastaví před vytvořením kontejneru, a použijte ji na obou místech: + +```php +public function bootWebApplication(): Nette\DI\Container +{ + return $this->bootConfigurator()->createContainer(); +} + +public function bootConfigurator(): Configurator +{ + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator; +} +``` + +V `mcp-bootstrap.php` pak bude `return (new App\Bootstrap)->bootConfigurator();`. + +- **Vlastní bootstrap** (konstruktor s argumenty, multi-tenant aplikace a podobně): `init` zapíše šablonu s komentářem `TODO`, kterou doplníte. Volání `new Configurator` nechte uvnitř třídy `Bootstrap`, aby dál fungovala autodetekce `%appDir%` v Nette, která se dívá na soubor, jenž Configurator vytváří. Parametry se pohodlně předávají proměnnými prostředí nastavenými v `.mcp.json`: + +```php +$blog = getenv('BLOG') === 'phpfashion' ? App\Blog::PhpFashion : App\Blog::LaTrine; +return (new App\Bootstrap($blog))->bootConsoleConfigurator(); +``` + +Debug režim zapínat nemusíte; inspektor to udělá sám, protože v CLI se nikdy neautodetekuje a právě debug režim umožňuje živé načítání změn konfigurace. + + +Nástroje +======== + +Nástroje jsou seskupené podle toho, na co se dívají. Každá skupina se objeví jen tehdy, když má vaše aplikace odpovídající část: bez `nette/database` nejsou žádné nástroje `db_*` a AI nematou nástroje, které nemohou fungovat. + + +Aplikace +-------- + +| Nástroj | Co dělá +|------|------ +| `app_get_info` | verze PHP a Nette, nainstalované balíčky Nette, adresáře a databázový driver + +AI má pokyn zavolat ho jako první, aby kód, který píše, odpovídal verzím, které skutečně používáte, například atributům PHP 8.3 nebo zvyklostem Nette 3.3. + + +DI kontejner +------------ + +| Nástroj | Co dělá +|------|------ +| `di_get_services` | vypíše služby s typy, tagy, aliasy a autowiringem, volitelně filtrované podřetězcem názvu nebo typu +| `di_get_service` | detaily jedné služby včetně toho, zda už byla vytvořena +| `di_find_by_type` | služby implementující třídu nebo rozhraní a kterou z nich vybere autowiring +| `di_find_by_tag` | služby nesoucí tag, s hodnotami tagu +| `di_get_parameter_names` | názvy parametrů, vnořené v tečkové notaci (`database.default.dsn`) +| `di_get_parameter` | hodnota jednoho parametru; tajemství (`password`, `token`, `dsn`, …) jsou maskována + +Když se zeptáte "jaké mám mailery?", AI zavolá `di_find_by_type("Nette\Mail\Mailer")` a vidí přesně to, co váš kontejner obsahuje. K dispozici jsou jen běhová data: inspektor zná typ, tagy a aliasy služby, ne výraz továrny ani volání `setup` z konfigurace. Tyto nástroje vyžadují `nette/di` 3.2.7 nebo novější; parametry navíc musí být exportované, a pokud máte v konfiguraci `di: export: parameters: no`, nástroje vám to řeknou. + + +Router +------ + +| Nástroj | Co dělá +|------|------ +| `router_get_routes` | všechny registrované routy s maskami, výchozími hodnotami a prefixy modulů +| `router_match_url` | který presenter a akce obsluhují URL, s parametry (např. `/article/123`) +| `router_generate_url` | URL pro presenter a akci, stejně jako to dělá `{link}` (např. `Article:show` s `{"id": 5}`) + +Inspektor běží bez HTTP požadavku, takže vaše aplikace nedokáže zjistit vlastní adresu tak, jak to dělá na webu. Řekněte jí ji v konfiguraci aplikace (`nette/http` 3.4): + +```neon +http: + baseUrl: https://example.com/ +``` + +Bez ní `router_generate_url` ohlásí chybu s návodem, co nastavit, a relativní URL předané do `router_match_url` se porovnávají vůči `http://localhost/`. + + +Databáze +-------- + +| Nástroj | Co dělá +|------|------ +| `db_get_tables` | tabulky a pohledy +| `db_get_columns` | sloupce tabulky: typy, nullabilita, výchozí hodnoty, primární a cizí klíče +| `db_get_relationships` | vztahy přes cizí klíče mezi všemi tabulkami (belongsTo, hasMany) +| `db_get_indexes` | indexy tabulky +| `db_query` | spustí jeden SQL příkaz s hodnotami navázanými na zástupné znaky `?`; ve výchozím stavu jen pro čtení +| `db_explain_query` | spustí `EXPLAIN` nad dotazem `SELECT` + +Tahle skupina ukončí hádání o vašem schématu. "Vygeneruj entitu pro tabulku product" se změní ve volání `db_get_columns("product")` a entitu se sloupci, které skutečně máte. + +`db_query` dovolí AI podívat se i na data, třeba jaké hodnoty sloupec se stavem opravdu obsahuje. Ve výchozím stavu přijímá jen příkazy typu `SELECT` (`SELECT`, `SHOW`, `EXPLAIN`, `DESCRIBE`, `WITH`, `VALUES`, `TABLE`, jediný příkaz, žádné `INTO OUTFILE`) a na MySQL, PostgreSQL a SQLite je spouští uvnitř transakce jen pro čtení, takže cokoli, co by validátor přehlédl, odmítne sama databáze. Hodnoty sloupců, jejichž názvy vypadají na tajemství, jsou maskované a počet řádků je omezený. + + +Tracy +----- + +| Nástroj | Co dělá +|------|------ +| `tracy_get_log` | nejnovější záznamy logu podle úrovně (`exception` ve výchozím stavu, `error`, `warning`, …), každý s názvem svého reportu +| `tracy_get_report` | report výjimky tak, jak ho Tracy píše pro agenty: kód kolem výjimky, stack trace s argumenty a prostředí + +S těmito dvěma se mění podoba ladění. Místo kopírování stack trace do chatu řeknete "podívej se do logu a řekni mi, co se rozbilo", a AI si výjimku přečte sama. Adresář logů je ten, do kterého píše Tracy logger vaší aplikace, není co nastavovat. Markdownové reporty vyžadují Tracy 2.12 nebo novější; starší reporty jen v HTML se přečíst nedají. + + +Zkoušení nástrojů z terminálu +============================= + +K tomu, abyste viděli, co nástroj vrací, nepotřebujete AI. Příkaz `call` spustí nástroj přesně tak, jak by to udělal klient, a výsledek vypíše jako JSON: + +```shell +vendor/bin/mcp-inspector call app_get_info +vendor/bin/mcp-inspector call router_match_url '{"url": "/article/123"}' +vendor/bin/mcp-inspector call db_query '{"query": "SELECT * FROM product WHERE id = ?", "params": [1]}' +``` + +Je to nejrychlejší způsob, jak zkontrolovat bootstrap a konfiguraci a podívat se na to, co uvidí AI. Volby `--project`, `--bootstrap` a `--config` fungují i tady. + + +Konfigurace +=========== + +Inspektor je sám o sobě malá Nette aplikace a `config/mcp-inspector.neon` je konfigurace jeho vlastního DI kontejneru, s obvyklými sekcemi `parameters:`, `services:` a jednou sekcí pro každou skupinu nástrojů. Každá sekce je nepovinná; chybějící soubor znamená výchozí hodnoty. Tohle je soubor, který `init` vygeneruje: + +```neon +# Configuration of nette/mcp-inspector: a Nette DI config for the inspector's own container. +# The inspector reads it itself, do not add it to the application's configs. +# Every section is optional; missing keys use the defaults shown here. + +inspector: + # false keeps the inspector from starting at all + enabled: true + # tool names or patterns hidden from the agent, e.g. [db_*, tracy_get_log] + disableTools: [] + +database: + # true: only SELECT-like statements, run in a read-only transaction + # false: any statement, the agent can modify data + readOnly: true + # maximum number of rows returned by db_query + rowLimit: 100 +``` + +Inspektor si tento soubor čte sám; nepřidávejte ho do konfigurace své aplikace. Změny se projeví po restartu MCP serveru, který AI nástroj dělá spolu se svou session. + + +Skrývání nástrojů +----------------- + +`disableTools` přijímá názvy nástrojů nebo vzory s `*`. Nechcete, aby AI vůbec četla vaše data? Skryjte celou databázovou skupinu: + +```neon +inspector: + disableTools: [db_*] +``` + + +Konfigurace databáze +-------------------- + +Jediná skutečná bezpečnostní otázka zní, zda AI smí měnit data, a ve výchozím stavu nesmí. Když to chcete, třeba na vývojové databázi, o kterou nejde, ochranu vypněte: + +```neon +database: + readOnly: false + rowLimit: 500 +``` + +S `readOnly: false` se spustí jakýkoli příkaz, včetně `UPDATE`, `DELETE` a DDL. AI nástroje jako Claude Code se vás pak před každým `db_query` zeptají na potvrzení, protože nástroj už o sobě netvrdí, že je jen pro čtení. + + +Jiné AI nástroje +================ + +MCP Inspector funguje s každým nástrojem, který mluví MCP. `init` ho zaregistruje pro Claude Code do `.mcp.json` a pro Cursor a VS Code, když v projektu najde jejich adresáře `.cursor` nebo `.vscode`. Pro jakýkoli jiný nástroj zaregistrujte příkaz, který spustí server přes standardní vstup a výstup: + +```json +{ + "mcpServers": { + "nette-inspector": { + "type": "stdio", + "command": "php", + "args": ["vendor/bin/mcp-inspector"] + } + } +} +``` + +Příkaz běží v kořeni projektu. Tam, kde to neplatí (některé editory spouštějí servery jinde), přidejte do argumentů `"--project=/cesta/k/projektu"`. Kde má konfigurační soubor ležet, najdete v dokumentaci svého AI nástroje. + + +Bezpečnost +========== + +Inspektor odhaluje DI graf, konfiguraci a data jakékoli aplikace, na kterou ho namíříte, proto ho miřte jen na vývojová prostředí a vývojová data. Proces v CLI nemá žádný spolehlivý způsob, jak poznat, že běží na produkčním serveru, a tak je ochrana vrstvená: + +1. **Vývojová závislost**: instalujte ho s `--dev` a `composer install --no-dev` na serveru ho nikdy nenainstaluje. +2. **Bezpečné výchozí hodnoty**: nic nemění data, tajemství jsou maskovaná, žádný nástroj nespouští PHP kód ani nezapisuje soubory. +3. **Vypínač**: `inspector: enabled: false` v `config/mcp-inspector.neon` nebo proměnná prostředí `MCP_INSPECTOR_DISABLED=1` způsobí, že server odmítne nastartovat. + +Dvě další věci se dějí potichu. Hodnoty pod klíči, které vypadají jako tajemství (`password`, `secret`, `token`, `apiKey`, `dsn`, …), vycházejí jako `***`, v parametrech i ve výsledcích dotazů. A výsledky nesoucí data z vaší aplikace, řádky z databáze a záznamy logu, jsou označené jako nedůvěryhodné, takže AI ví, že nemá poslouchat instrukce, které by v nich našla; uživatelský komentář "ignoruj své předchozí instrukce" zůstane jen komentářem. + + +Vlastní toolkity +================ + +Vaše aplikace má i vlastní fakta, která by AI ráda znala: čekající objednávky, feature flagy, tenanty. Přidejte toolkit: třídu implementující `Nette\McpInspector\Toolkit`, jejíž veřejné metody označené `#[McpTool]` se stanou nástroji. Docblock je popis nástroje, pište ho tedy pro AI: co nástroj vrací a kdy ho volat. + +```php +namespace App\Mcp; + +use Mcp\Capability\Attribute\McpTool; +use Mcp\Schema\ToolAnnotations; +use Nette\McpInspector\AppContainer; +use Nette\McpInspector\Toolkit; +use Nette\McpInspector\UntrustedData; + +class BlogToolkit implements Toolkit +{ + public function __construct( + private AppContainer $app, + ) {} + + public function isAvailable(): bool + { + return true; + } + + /** + * Get a blog post by ID. + * @param int $id Post ID + */ + #[UntrustedData] + #[McpTool(name: 'blog_get_post', title: 'Blog post', annotations: new ToolAnnotations(readOnlyHint: true))] + public function getPost(int $id): array + { + $post = $this->app->get()->getByType(BlogFacade::class)->getPost($id); + return $post ? ['id' => $post->id, 'title' => $post->title] : ['error' => 'not found']; + } +} +``` + +Několik věcí stojí za povšimnutí. Toolkit závisí na `AppContainer`, jehož `get()` vrací aktuální kontejner vaší aplikace, takže se respektuje znovunačtení konfigurace; přes něj se dostanete k jakékoli službě. `isAvailable()` dovolí toolkitu ustoupit, když aplikaci chybí to, co potřebuje. Atribut `#[UntrustedData]` označuje nástroj, jehož výsledek nese data z aplikace (příspěvky, komentáře, uživatelský vstup), a inspektor pak AI řekne, aby instrukce v nich neposlouchala. A `readOnlyHint: true` říká AI nástroji, že volání je bezpečné a nemusí se vás pokaždé ptát. + +Toolkit zaregistrujte jako službu v konfiguraci inspektoru, ne v konfiguraci aplikace: + +```neon +# config/mcp-inspector.neon +services: + - App\Mcp\BlogToolkit +``` + +AI teď může volat `blog_get_post` jako kterýkoli vestavěný nástroj. Vyzkoušejte ho nejdřív z terminálu: `vendor/bin/mcp-inspector call blog_get_post '{"id": 1}'`. + +{{composer: nette/mcp-inspector}} +{{repo: nette/mcp-inspector}} diff --git a/ai/cs/tips.texy b/ai/cs/tips.texy new file mode 100644 index 0000000000..d348348e9a --- /dev/null +++ b/ai/cs/tips.texy @@ -0,0 +1 @@ +{{redirect: vibe-coding}} diff --git a/ai/cs/vibe-coding.texy b/ai/cs/vibe-coding.texy new file mode 100644 index 0000000000..4307f95ba2 --- /dev/null +++ b/ai/cs/vibe-coding.texy @@ -0,0 +1,298 @@ +Vibe coding v praxi +******************* + +
+ +Vibe coding znamená, že běžnou řečí popíšete, co chcete, a PHP za vás napíše agent. Je to mocný způsob práce, ale jako každý nástroj vydá víc, když víte, jak na něj. Tahle stránka shrnuje praktické rady z reálné práce s Nette. + +- Jak psát prompty, které fungují +- Jak z MCP Inspectoru vytěžit maximum +- Osvědčené postupy pro běžné úkoly +- Čeho se vyvarovat + +
+ + +Jak psát lepší prompty +====================== + + +Umění být konkrétní +------------------- + +Nejvíc ze všeho pomůže konkrétnost. Porovnejte tyhle dva prompty: + +**Mlhavý prompt:** + +``` +Vytvoř formulář +``` + +Tím AI skoro žádný kontext nedáváte. Jaký formulář? S jakými poli? S jakou validací? AI si musí zbytek domyslet a nemusí se trefit do toho, co potřebujete. + +**Konkrétní prompt:** + +``` +Vytvoř ProductForm s těmito poli: +- name: textové pole, povinné, maximálně 100 znaků +- price: desetinné číslo, povinné, musí být kladné +- description: textarea, nepovinné +- category: select z entit CategoryRow + +Použij vykreslování pro Bootstrap 5. Formulář má sloužit jak pro zakládání nových produktů, tak pro editaci stávajících. +``` + +Teď AI přesně ví, co potřebujete, a vygeneruje kód, který funguje napoprvé. + + +Odkazujte na existující vzory +----------------------------- + +Váš kód už nějaké vzory má. Místo abyste je popisovali, ukažte AI příklad: + +``` +Vytvoř OrderPresenter. Drž se stejných vzorů jako ProductPresenter: +stejná struktura, stejná práce s formuláři, stejné uspořádání šablon. +``` + +AI si ProductPresenter přečte a zopakuje postupy, které už používáte. + + +Nechte těžkou práci na MCP +-------------------------- + +Pokud máte nainstalovaný MCP Inspector (a měli byste), nepopisujte AI svou aplikaci. Ať si ji zjistí sama: + +**Místo tohohle:** + +``` +Moje tabulka product má sloupce: id (int), name (varchar), price (decimal), +category_id (int, cizí klíč do category), created_at (datetime)... +``` + +**Řekněte prostě:** + +``` +Vygeneruj entitu pro tabulku product. +``` + +AI zavolá `db_get_columns("product")` a uvidí skutečné schéma. Vygenerovaná entita bude odpovídat vaší reálné databázi včetně sloupců, které byste možná zapomněli zmínit. + + +Řekněte, o co vám jde +--------------------- + +AI vám nevidí do hlavy. Pokud má váš požadavek nějaký důvod, podělte se o něj: + +``` +Potřebuju zrychlit výpis produktů. Teď se načítají všechny najednou, +což je při tisících položek pomalé. +Stránka musí umět filtrovat podle kategorie a řadit podle ceny nebo názvu. +``` + +Díky tomu AI navrhne vhodné řešení (stránkování, líné načítání, cache), místo aby slepě implementovala, co jste jí zadali. + + +Práce s MCP Inspectorem +======================= + +MCP Inspector vynikne, když ho použijete s rozmyslem. + + +Nejdřív prozkoumejte, pak stavte +-------------------------------- + +Začínáte novou funkci? Nechte AI, ať se nejdřív zorientuje: + +``` +Chystám se přidat sledování objednávek. Než začneme: +1. Jaké mám služby související s objednávkami? +2. Jak vypadá moje tabulka order? +3. Které routy obsluhují stránky s objednávkami? +``` + +AI tím získá kontext o vašem stávajícím kódu a nová funkce do něj přirozeně zapadne. + + +Chytřejší ladění +---------------- + +Když se něco pokazí, nepopisujte chybu. Nechte AI, ať si ji přečte: + +``` +Něco se rozbilo. Podívej se do logu Tracy na poslední výjimku +a řekni mi, co se stalo. +``` + +AI zavolá `tracy_get_log()` a `tracy_get_report()`, přečte si stack trace a často odhalí příčinu rychleji, než byste ji stihli popsat. + + +Ověřte, než začnete tvořit +-------------------------- + +Než přidáte nové routy nebo odkazy, ověřte si, co už existuje: + +``` +Který presenter obsluhuje /admin/products/edit? Chci mít jistotu, +že nevytvářím něco, co by kolidovalo. +``` + + +Osvědčené postupy +================= + +Tady jsou vyzkoušené přístupy k běžným úkolům. + + +Kompletní CRUD +-------------- + +Nežádejte kousek po kousku. Dejte AI úplné zadání: + +``` +Vytvoř kompletní CRUD pro správu produktů: + +1. ProductPresenter s akcemi: list, add, edit, delete +2. ProductForm jako komponentu (pro přidání i editaci) +3. Latte šablony pro všechny akce +4. Návrh rout + +Vyjdi ze skutečného schématu tabulky product. Drž se postupů +z CategoryPresenter, pokud existuje. +``` + + +Přidání nové funkce +------------------- + +Rozdělte ji na fáze, které na sebe navazují: + +``` +Potřebuju k produktům přidat zákaznické recenze. + +Fáze 1 - datová vrstva: +- Podívej se na tabulku product +- Navrhni schéma tabulky review +- Vytvoř entitu ReviewRow + +Fáze 2 - business logika: +- Vytvoř ReviewService pro CRUD operace +- Přidej metody pro načtení recenzí k produktu + +Fáze 3 - UI: +- Přidej výpis recenzí do ProductPresenter:detail +- Vytvoř ReviewForm pro odesílání recenzí +``` + + +Refaktoring existujícího kódu +----------------------------- + +Nechte AI, ať kódu nejdřív porozumí: + +``` +Zanalyzuj třídu OrderService. Co dělá která metoda? +Vidíš tam nějaké code smells nebo prostor ke zlepšení? +``` + +A potom: + +``` +Metoda calculateTotal toho dělá příliš. Rozděl ji na menší metody +a zachovej přitom stejné veřejné rozhraní. +``` + + +Čemu se vyhnout +=============== + + +Nekontrolovat vygenerovaný kód +------------------------------ + +AI generuje kód rychle, ale to neznamená, že je každý řádek dokonalý. Vždycky projděte: + +- **Databázové dotazy** - Jsou efektivní? Nechybí jim indexy? +- **Bezpečnost** - Validuje se vstup? Kontrolují se oprávnění? +- **Hraniční případy** - Co se stane při prázdných datech? Při hodnotách null? + +AI je opravdu dobrá, ale pořád je to asistent. Za výsledný kód zodpovídáte vy. + + +Ignorovat hlášení linteru +------------------------- + +Pokud používáte [pluginy pro Claude Code |agent-plugins], kontrolují vám automaticky soubory PHP, Latte, NEON, JSON i JavaScript. Když některý ohlásí chybu, AI o ní ví. Místo ručního opravování prostě řekněte: + +``` +Oprav chybu, kterou jsi právě udělal. +``` + +AI si výstup kontroly přečte a chybu opraví. + + +Zapomenout zaregistrovat službu +------------------------------- + +Když AI vytvoří novou třídu služby, občas ji zapomene zaregistrovat do DI kontejneru. Pokud narazíte na chybu "Service not found", zeptejte se: + +``` +Co musím změnit v services.neon kvůli nové OrderExportService? +``` + + +Chtít všechno najednou +---------------------- + +AI zvládne i složité úkoly, ale někdy pomůže rozdělit je na menší: + +**Příliš ambiciózní:** + +``` +Postav mi kompletní e-shop s katalogem produktů, +košíkem, objednávkovým procesem, platbami, sledováním objednávek a administrací. +``` + +**Lepší přístup:** + +``` +Pojďme postavit e-shop krok za krokem. Začni katalogem produktů: +potřebuju výpis, detail a správu produktů. +``` + + +V čem AI vyniká a v čem ne +========================== + + +AI je skvělá na +--------------- + +- **Rutinní kód** - presentery, formuláře, entity, základní šablony +- **Držení se vzorů** - "udělej to jako X, ale pro Y" +- **Porozumění kódu** - "co dělá tahle metoda?" +- **Psaní testů** - testy k hotové implementaci +- **Refaktoring** - lepší struktura kódu při zachování chování + + +Ručně raději pište +------------------ + +- **Složitou business logiku** - doménová pravidla, která je potřeba promyslet +- **Výkonově kritický kód** - algoritmy, které potřebují optimalizaci +- **Bezpečnostně citlivý kód** - přihlašování, oprávnění, šifrování +- **Netradiční řešení** - věci, které se nedrží existujících vzorů + +AI je násobič, ne náhrada. Dobré vývojáře zrychlí, ale pořád potřebuje, aby ji dobrý vývojář vedl. + + +Závěrem +======= + +Vývoj s AI se nejlíp naučíte praxí. Začněte jednoduchými úkoly, všímejte si, co funguje, a postupně se pouštějte do složitějších projektů. + +A pamatujte: AI je váš asistent. Vývojář jste pořád vy. Vy rozhodujete, vy kontrolujete kód a vy zodpovídáte za kvalitu výsledku. + +Hodně zdaru! + +{{description: Praktické rady pro vibe coding v PHP s Nette: jak psát prompty, které fungují, jak využít MCP Inspector, osvědčené postupy a čeho se vyvarovat.}} diff --git a/ai/en/@home.texy b/ai/en/@home.texy new file mode 100644 index 0000000000..07a0bf2cd1 --- /dev/null +++ b/ai/en/@home.texy @@ -0,0 +1,97 @@ +Vibe Coding with Nette +********************** + +
+ +An AI agent will write you a hundred lines of PHP in a few seconds. Whether those hundred lines actually run is decided by something the agent cannot supply on its own: a foundation it can read, verify and build on. + +Nette has been building that foundation for years - back when it still looked like a detail only purists cared about. + +- **Typed to the bone** - the agent reads types instead of guessing them +- **Clear conventions** - it knows where every class belongs +- **A closed loop** - it sees your real application and checks its own work + +
+ + +The IDE Was for You. Types Are for the Agent. +============================================= + +For years we judged our tools by how well they helped a human: autocomplete, a type popping up on hover, jump-to-definition. An agent never looks at your screen. It needs information it can read as plain text and, above all, verify on its own. + +That is the whole difference between an IDE and static analysis. PhpStorm discreetly underlines a place where you might be passing `null`. PHPStan prints the same thing to the console as an error the agent reads, understands and fixes - without you sitting there watching. + +So the question stopped being "how nicely does my editor describe this API?" and became "how much does the code say about itself?" + + +Nette Says a Lot +================ + +When PHP 7.1 completed the type system with nullable types, Nette was the first full-stack framework to type its entire API through, `declare(strict_types=1)` in every file included. Typed properties followed the moment PHP 7.4 shipped them. Whatever the language could express, Nette used - usually before anyone else. + +And where PHP itself falls short, annotations take over. Native types cannot say "a non-empty list of strings" or "the return value depends on the argument". Nette says both, right where it matters: + +```php +$data = $form->getValues(RegistrationData::class); +echo $data->email; // $data is a RegistrationData, fully typed +``` + +There is no native PHP type for "if you hand me a class name, I hand you back an instance of it". A conditional return type says exactly that, and static analysis, your IDE and the agent all read it. Write `$data->emial` and the mistake surfaces before the code is ever run. + +The same care runs through the whole framework: array shapes, `class-string`, `positive-int`, `non-empty-string`. **Every Nette package passes PHPStan at level 8** - not in parts, not with a pile of exceptions. You are building on a foundation typed as rigorously as it makes sense to type anything, and a well-typed foundation is what makes a well-typed application easy. + +.[note] +The full story is in the article [Your AI Agent Is Only as Good as Your Types |https://blog.nette.org/en/ai-agent-only-as-good-as-your-types]. + + +It's All About the Loop +======================= + +An agent is only as good as the feedback it gets, and the best feedback is a loop. It writes something, runs the analysis and the tests, reads the result, fixes the error and tries again. Write, check, fix, repeat. That loop is the moment a text generator turns into something that genuinely tunes code into working shape. + +Nette hands the agent every part of it: + +- **PHPStan** with the [Nette extension |tools:phpstan-rules], which teaches the analyzer framework specifics - it knows that `$this['menu']` is your `MenuControl`, it infers the control type from `addText()`, it understands `#[Inject]` +- **[Nette Tester |tester:]** for the tests. Don't have any yet? That is the very first thing to have the agent write, because without them it cannot tell that it just broke something. +- **Automatic linting** of PHP, Latte, NEON, JSON and JavaScript after every single edit, so a broken template never makes it as far as the browser + + +Your Application, Not a Guess +============================= + +Ask any AI to "generate an entity for the product table" and it will guess your columns from patterns it picked up during training. Except your price column is called `unit_price`, and there is a `currency_id` you never mentioned. + +**[MCP Inspector |mcp-inspector]** takes the guessing away. It is a server that lets the agent look straight into your running application: the services in your DI container, your real database schema, your routes, your Tracy logs. Everything it exposes is strictly read-only, and it works from any editor that speaks the Model Context Protocol: Claude Code, Cursor, VS Code and others. + +Debugging changes shape too. Instead of copying a stack trace into a chat window, you say "check the log and tell me what broke" - and the agent reads the exception itself. + + +Nette Knowledge, Built In +========================= + +The **[Claude Code plugin |agent-plugins]** supplies the part that is nowhere in your codebase: how things are actually done in Nette. A whole family of skills covering architecture, forms, database, components, security, Latte, NEON and more, each one switching itself on when the conversation turns to its topic. Ask about form validation and the forms skill wakes up. Ask about a BlueScreen and the Tracy one does. + +What comes out reads like an experienced Nette developer wrote it, not like generic PHP stitched together from snippets. + + +Get Started +=========== + +The whole setup takes about ten minutes: + +1. [Install your AI tool |getting-started] - we recommend Claude Code +2. [Add the Nette plugin |agent-plugins] so the agent knows the framework +3. [Set up MCP Inspector |mcp-inspector] so it knows *your* application + +Then describe what you want, close the agent into the loop, and let it work. When you want to get more out of your agent, read [Vibe Coding in Practice |vibe-coding]. + + +When Your Application Needs AI Too +================================== + +This whole page is about AI that helps you write code. Inside your application, AI can do something quite different: summarize an article, sort incoming messages, pull invoice data into your database. + +[AI Access |https://ai-access.deegee.dev] is the library for that: a single PHP interface to OpenAI, Claude, Gemini, DeepSeek and Grok. Switching between them is a one-line change. And since it has no dependencies, it brings no vendor SDK and no version conflicts into your project. + +{{maintitle: Vibe Coding with Nette – The PHP Framework AI Agents Can Read}} +{{description: AI agents guess as much as your code lets them. Nette is typed to the bone, passes PHPStan at level 8, and gives your agent a loop it can verify.}} diff --git a/ai/en/@left-menu.texy b/ai/en/@left-menu.texy new file mode 100644 index 0000000000..6911f9351d --- /dev/null +++ b/ai/en/@left-menu.texy @@ -0,0 +1,5 @@ +- [Overview |@home] +- [Getting Started |getting-started] +- [Claude Code Plugin |agent-plugins] +- [MCP Inspector |mcp-inspector] +- [Vibe Coding in Practice |vibe-coding] diff --git a/ai/en/@meta.texy b/ai/en/@meta.texy new file mode 100644 index 0000000000..e06cc9886c --- /dev/null +++ b/ai/en/@meta.texy @@ -0,0 +1 @@ +{{sitename: Nette AI}} diff --git a/ai/en/agent-plugins.texy b/ai/en/agent-plugins.texy new file mode 100644 index 0000000000..61b46f4427 --- /dev/null +++ b/ai/en/agent-plugins.texy @@ -0,0 +1,416 @@ +Claude Code Plugin +****************** + +
+ +The "Nette plugin for Claude Code":https://github.com/nette/agent-plugins gives deep knowledge of the framework. Instead of generic PHP advice, you get recommendations that follow Nette conventions - from presenters and forms to Latte templates and database queries. + +Together with its companion plugins, you get: +- A **broad set of skills** covering all major areas of Nette development +- **Automatic validation** of PHP, Latte, NEON, JSON and JavaScript files (the `nette-lint` plugin) +- **MCP Inspector integration** for real-time application introspection + +
+ + +Installation +============ + +If you haven't installed Claude Code yet, see the [complete setup guide |getting-started]. Once Claude Code is running, install the Nette plugin: + +```shell +/plugin marketplace add nette/agent-plugins +/plugin install nette@nette +``` + +For automatic validation of your PHP, Latte, NEON, JSON and JavaScript files after each edit, add the companion linter plugin: + +```shell +/plugin install nette-lint@nette +``` + +The full family of plugins: + +| Plugin | Purpose | +|--------|---------| +| `nette` | Skills with deep framework knowledge | +| `nette-lint` | Automatic validation of PHP, Latte, NEON, JSON and JavaScript | +| `php-fixer` | Automatic PHP code style fixing (optional) | +| `nette-dev` | Coding standards for framework contributors | + + +How Skills Work +=============== + +You don't need to activate skills manually. They turn on automatically based on what you're talking about. + +- Ask about "presenter structure" → the `nette-architecture` skill activates +- Ask about "form validation" → the `nette-forms` skill activates +- Ask about "Latte filters" → the `latte-templates` skill activates +- Ask about a "BlueScreen error" → the `tracy-debugging` skill activates + +This means you get relevant, context-aware help without having to think about which skill you need. + + +Available Skills +================ + +Here's what each skill covers: + + +nette-architecture +------------------ + +When you're designing your application structure, this skill guides you through: + +- **Directory organization** - Where to put presenters, services, entities, and components +- **Module design** - How to split your application into logical modules (Admin, Front, Api) +- **Presenter patterns** - When to use base presenters, how to handle authentication +- **Evolution strategy** - Start minimal, grow organically, refactor when needed + +The key principle: Don't over-engineer. Create subdirectories when you have 5+ related files, not before. + + +nette-routing +------------- + +URL masks and the router: + +- **Masks** - Optional parts, regular-expression constraints, defaults +- **Order** - Why the first matching route also decides which URL gets generated +- **Filters** - Slug to id mapping, one-way routes for retired addresses +- **Canonical URLs** - When the redirect fires and how to switch it off + +Example: Claude knows that a catch-all route placed first will quietly hijack every generated link. + + +nette-configuration +------------------- + +Everything about the DI container and NEON configuration: + +- **Service registration** - How to define services in `services.neon` +- **Autowiring** - When it works automatically and when you need explicit configuration +- **Parameters** - How to use configuration parameters across your application +- **Extensions** - Working with DI extensions from Nette and third parties + + +nette-database +-------------- + +Covers both the raw SQL approach and the Database Explorer: + +- **Database Explorer** - Using `Selection` for queries, `ActiveRow` for entities +- **Entity conventions** - The `Row` suffix pattern, type hints with `@property-read` +- **Relationships** - Navigating foreign keys with colon notation +- **When to use what** - Explorer for CRUD, raw SQL for complex analytics + +Example: Claude knows that `->where('category.slug', $slug)` automatically joins the category table. + + +nette-forms +----------- + +Creating and handling forms the Nette way: + +- **Controls** - All built-in controls from text inputs to file uploads +- **Validation** - Built-in rules, custom validators, conditional validation +- **Rendering** - Manual rendering, Bootstrap integration, custom renderers +- **Patterns** - Create/edit forms, form components, AJAX submissions + + +nette-components +---------------- + +Components, signals and AJAX: + +- **Factories** - `createComponent()`, lazy creation, the component tree +- **Signals** - `handle()`, where they run, links inside a component +- **Snippets** - `redrawControl()`, snippet areas, dynamic snippets +- **Persistent parameters** - The `#[Persistent]` attribute and persistent components + +Example: Claude knows that inside a component template `n:href` always means a signal, and that reaching a presenter needs `{plink}`. + + +nette-security +-------------- + +Authentication and authorization: + +- **Login** - The `Authenticator` interface, identities, error codes +- **Passwords** - Hashing as a service, rehashing on login +- **Permissions** - Roles, resources, and the `Permission` ACL +- **Configuration** - The `security:` section + +Example: Claude knows that `logout()` keeps the identity unless you ask it not to, so "has an identity" is not the same as "is logged in". + + +nette-http +---------- + +The request, the response and sessions: + +- **Request** - `UrlScript`, cookies, uploads, and the reverse-proxy trap +- **Sessions** - Sections with `set()` / `get()`, per-key expiration +- **Same-origin protection** - How CSRF defense works and how to allow a legitimate exception +- **SSRF** - Validating a URL that came from a user + +Example: Claude knows that a link from an e-mail counts as direct navigation, so a signal reached that way needs to be marked explicitly. + + +nette-caching +------------- + +Caching with nette/caching: + +- **Dependencies** - Expiration, tags, files, and sliding expiration +- **Invalidation** - Cleaning by tag, and which storages need a journal for it +- **Storages** - File, Memcached, SQLite, memory, and the null one for tests + +Example: Claude knows that tags silently need a journal, and that the constant is `Cache::Expire`. + + +nette-mail +---------- + +Sending e-mail: + +- **Message** - Recipients, HTML body with automatic image embedding, attachments +- **Mailers** - SMTP with OAuth 2.0, fallback, and writing to files in development +- **DKIM** - Signing outgoing mail + +Example: Claude knows that `setHtmlBody()` embeds images referenced in the HTML by itself, so no manual `cid:` juggling is needed. + + +nette-schema +------------ + +Data validation and normalization with the Schema component: + +- **Expect class** - Building validation schemas for arrays and objects +- **Configuration schemas** - Validating NEON configuration in DI extensions +- **Type coercion** - Automatic conversion of strings to integers, dates, etc. +- **Custom validators** - Adding your own validation rules + +Example: Claude knows how to create schemas like: + +```php +$schema = Expect::structure([ + 'name' => Expect::string()->required(), + 'age' => Expect::int()->min(0)->max(120), + 'email' => Expect::string(), + 'roles' => Expect::listOf('string')->default([]), +]); +``` + + +nette-tester +------------ + +Writing tests with Nette Tester: + +- **Test structure** - The `.phpt` format, `@testCase` annotation, file organization +- **Assertions** - All `Assert::*` methods: `same()`, `equal()`, `exception()`, `match()`, and more +- **Fixtures** - Setting up test data, mocking dependencies, database transactions +- **Running tests** - Command-line options, parallel execution, code coverage + +Example: Claude can generate proper test files: + +```php +/** @testCase */ +class UserServiceTest extends TestCase +{ + public function testCreateUser(): void + { + $service = new UserService($this->mockDatabase()); + $user = $service->create(['name' => 'John']); + Assert::same('John', $user->name); + } +} +``` + + +tracy-debugging +--------------- + +Working with the Tracy debugger: + +- **BlueScreen** - Reading exception details, stack traces, source highlights +- **Tracy Bar** - Interpreting embedded SQL query logs, timing, and memory usage at the bottom of every page +- **`dump()` workflow** - Inserting `dump($variable)` calls and reading the output back through curl or the browser +- **Production logs** - Investigating `log/exception-*.html` snapshots and `log/error.log` +- **Chrome MCP integration** - Pulling Tracy errors out of the browser console with `list_console_messages()` instead of taking screenshots + +Especially useful for 500 errors, blank pages, N+1 query issues, or whenever Claude fetches a local PHP URL and needs to interpret what came back. + + +nette-utils +----------- + +The utility classes that make PHP development easier: + +- **Arrays** - `Nette\Utils\Arrays` with `get()`, `getRef()`, `map()`, `flatten()`, and more +- **Strings** - Unicode-safe operations: `webalize()`, `truncate()`, `contains()`, `startsWith()` +- **Finder** - File system traversal with filtering by name, size, date +- **Image** - Resize, crop, sharpen with automatic format detection +- **Json** - Safe JSON encoding/decoding with proper error handling +- **Validators** - Email, URL, numeric validation helpers +- **DateTime** - Immutable date/time with Czech locale support + + +frontend-development +-------------------- + +Integrating frontend tools with Nette: + +- **Vite** - Setting up Vite for modern JavaScript/TypeScript development, HMR configuration +- **Nette Assets** - Using the asset system for cache-busted URLs in production +- **Tailwind CSS** - Configuration for Tailwind with Latte templates, purging unused styles +- **ESLint** - Code quality integration via `@nette/eslint-plugin` +- **Build scripts** - npm/package.json scripts for development and production builds + +Claude can help configure `vite.config.js` to work with Nette's directory structure and generate proper asset references in Latte templates. + + +latte-templates +--------------- + +Everything about the Latte templating engine: + +- **Syntax** - Tags, filters, blocks, and inheritance +- **Security** - Auto-escaping, content-aware output +- **Custom filters** - Creating and registering your own filters +- **Template classes** - Using typed templates for better IDE support + + +neon-format +----------- + +The NEON configuration format: + +- **Syntax** - Mappings, sequences, entities, and multiline strings +- **Common patterns** - Service definitions, parameter references +- **Debugging** - Finding and fixing syntax errors + + +nette-upgrading +--------------- + +A lookup table of retired names: + +- **Renames** - `I`-prefixed interfaces, UPPER_CASE constants, moved classes +- **Silent survivals** - Where the old name still works, so the mistake never surfaces +- **Changed signatures** - Arguments that disappeared or became named + +Example: Claude knows that `Nette\Database\Context` still resolves as an alias, so code written from memory keeps running while quietly using a retired name. + + +Automatic Validation +==================== + +One of the most useful features is automatic validation, provided by the separately installed **nette-lint** plugin. After every file edit it checks for errors: + +| What | How It Works | +|------|--------------| +| **PHP** | Runs `php -l` to check syntax after every `.php` or `.phpt` edit | +| **Latte templates** | Runs `latte-lint` to check syntax after every `.latte` edit | +| **NEON files** | Validates NEON syntax after every `.neon` edit | +| **JSON / JSONC** | Validates syntax after every `.json` or `.jsonc` edit and reports the exact line; comments and trailing commas are tolerated in JSONC files like `tsconfig.json` or `.vscode/*.json` | +| **JavaScript / TypeScript** | Runs `eslint --fix` after every `.js`, `.ts`, `.mjs` or `.mts` edit (only if the project has an ESLint config) | + +If there's a syntax error in any of these files, Claude knows about it immediately and can suggest a fix. No more discovering errors in the browser. + +The plugin looks for the project's tools and configs (`latte-lint`, `vendor/bin/neon-lint`, the ESLint config) upwards from the edited file, not just in the folder where Claude Code is running. So even if you keep several applications in one repository and run Claude Code from its root, each file is still checked with the right tool for its application. The search stops one level above the nearest `.git` folder, so tools from unrelated projects elsewhere on the disk are never picked up. + +Sometimes you don't want a file checked - for example `fixtures` with intentionally broken templates or code used in tests. You can skip specific paths by creating a `.nette-claude.json` file in your project, where each key is a hook name with a list of paths to ignore: + +```json +{ + "lint-php": { + "exclude": ["fixtures*"] + }, + "lint-latte": { + "exclude": ["tests/**/expected"] + } +} +``` + +Patterns work like in `.gitignore`: a name without a slash (such as `fixtures*`) matches at any depth, while a pattern with a slash is anchored to the folder containing the config file. + + +Plugin for Framework Contributors +================================= + +If you're contributing to Nette itself, there's an additional plugin with coding standards: + +```shell +/plugin install nette-dev@nette +``` + +| Skill | What It Covers | +|-------|----------------| +| `php-coding-standards` | Nette's PHP coding style - indentation, naming, structure | +| `php-doc` | PHPDoc conventions - when to document, what format to use | +| `commit-messages` | How to write commit messages for Nette repositories | +| `phpstan-analysis` | How to resolve PHPStan errors - prefer refactoring over phpDoc annotations over ignoring - plus common Nette patterns and baseline management | + + +Automatic PHP Style Fixing +========================== + +For automatic code style fixing after every PHP file edit, install the optional php-fixer plugin: + +```shell +/plugin install php-fixer@nette +/install-php-fixer +``` + +The second command installs `nette/coding-standard` globally. After that, every PHP file you edit will be automatically formatted according to Nette coding standards. + +Because the fixer **rewrites your files**, it's important to keep it away from code you don't want reformatted - typically `fixtures` with intentionally crafted formatting. Exclude such paths with a `fix-php-style` entry in `.nette-claude.json`: + +```json +{ + "fix-php-style": { + "exclude": ["fixtures*"] + } +} +``` + +The patterns follow the same gitignore-like rules as the [validation hooks |#automatic-validation] above. + +By default the fixer runs a single preset, the one matching your project's PHP version. A `presets` list defines exactly which presets run and in what order, where the name `php` stands for that version-derived preset. This is how you add `optimize-fn`, which maintains grouped `use function` and `use const` imports for compiler-optimized functions: + +```json +{ + "fix-php-style": { + "presets": ["php", "optimize-fn"] + } +} +``` + +Each entry is a separate pass over the edited file, so drop `php` from the list only if you really want the standard preset gone. + +The plugin also comes with the `php-auto-fixer` skill, which teaches Claude about one subtle pitfall: the fixer removes unused `use` statements, so a `use` added in a separate edit before the code that references it gets stripped. Claude knows to add imports together with the code that uses them. + + +MCP Inspector Integration +========================= + +The plugin works even better with [MCP Inspector |mcp-inspector] - a tool that lets Claude see your actual application state. With MCP Inspector, Claude can: + +- Query your real database schema instead of guessing +- List your registered DI services and their configuration +- Read Tracy error logs for debugging +- Match URLs to presenters using your actual routes + +Install it with two commands: + +```shell +composer require --dev nette/mcp-inspector:@dev +vendor/bin/mcp-inspector init +``` + +Then restart Claude Code. See the [MCP Inspector documentation |mcp-inspector] for all the available tools. + +{{composer: nette/agent-plugins}} diff --git a/ai/en/getting-started.texy b/ai/en/getting-started.texy new file mode 100644 index 0000000000..e8c011c1d4 --- /dev/null +++ b/ai/en/getting-started.texy @@ -0,0 +1,260 @@ +Getting Started +*************** + +
+ +Ready to try [vibe coding |@home] with Nette? This guide walks you through the complete setup: + +- Choosing and installing an AI tool +- Setting up MCP Inspector so AI can see your application +- Making your first AI-assisted changes + +The whole process takes about 10 minutes. Let's get started! + +
+ + +Choosing Your AI Tool +===================== + +Nette AI tools work with any MCP-compatible AI assistant. We recommend **Claude Code** for the best experience - it has a dedicated Nette plugin with deep framework knowledge and automatic code validation. + +Other options include **Cursor**, **VS Code**, and other MCP-compatible tools. See [Other AI Tools |#other-ai-tools] at the end of this guide. + + +What You'll Need +================ + +Before we begin, make sure you have: + +- **A Nette project** - existing or new (`composer create-project nette/web-project`) +- **PHP 8.3+** - required for MCP Inspector +- **An AI tool** - we'll install Claude Code below (Claude Pro costs $20/month) + + +Installation on macOS and Linux +=============================== + +```shell +curl -fsSL https://claude.ai/install.sh | bash +``` + +On macOS, you can alternatively use Homebrew: + +```shell +brew install --cask agent-plugins +``` + +After installation, skip to [Starting Claude Code |#starting-agent-plugins]. + + +Installation on Windows via WSL +=============================== + +Claude Code requires a Unix environment. On Windows, use WSL: + +```shell +wsl --install +``` + +This installs Ubuntu. **Restart your computer** after installation. + +After restart, launch Ubuntu: + +```shell +ubuntu +``` + +The first launch will ask for a username and password - you'll need them for `sudo` later. + +Installing Claude Code in WSL in the Ubuntu terminal: + +```shell +curl -fsSL https://claude.ai/install.sh | bash +``` + +Your Windows drives are mounted under `/mnt/`: +- `C:\Users\Jan\Projects` → `/mnt/c/Users/Jan/Projects` +- `D:\Work` → `/mnt/d/Work` + +From Windows, access Linux files via `\\wsl$\Ubuntu\home\username` in Explorer. + + +Starting Claude Code +==================== + +Great, you have Claude Code installed! Let's start it up. + +Navigate to your project directory: + +```shell +# Windows (WSL) +cd /mnt/c/Users/Jan/Projects/my-app + +# macOS/Linux +cd ~/projects/my-app +``` + +Start Claude Code: + +```shell +claude +``` + +The first time you run it, Claude Code will ask you to authenticate. It will open a browser window where you can log in to your Anthropic account. After successful authentication, you'll see the `claude>` prompt and you're ready to go. + +You can also use Claude Code "on the web":https://claude.ai/code or via the "desktop app":https://claude.com/download, but local installation provides the best experience with direct file access. + + +Adding the Nette Plugin +======================= + +Now let's give Claude deep knowledge of Nette. First, add the Nette marketplace and enable auto-updating: + +```shell +/plugin marketplace add nette/agent-plugins +``` + +Then install the plugin: + +```shell +/plugin install nette@nette +``` + +That's it! The plugin is now active. It includes 11 specialized skills that automatically activate based on what you're working on. When you ask about forms, it knows about Nette Forms. When you ask about templates, it knows about Latte. + + +Setting Up MCP Inspector +======================== + +The final piece is MCP Inspector, which lets Claude see your actual application - your services, database schema, routes, and error logs. + +.[caution] +MCP Inspector is still in **early development and has no stable release yet**. Install the development version (`:@dev` below) and expect tool names and configuration to keep changing. + +Two commands do the whole job: + +```shell +composer require --dev nette/mcp-inspector:@dev +vendor/bin/mcp-inspector init +``` + +`init` generates `mcp-bootstrap.php` (the script through which the inspector builds your application's DI container), the inspector's configuration in `config/mcp-inspector.neon`, and registers the server for Claude Code in `.mcp.json`. It recognizes the usual shapes of `App\Bootstrap`; if yours is unusual, the generated file contains a `TODO` comment telling you what to complete. See [the MCP Inspector page |mcp-inspector#the-mcp-bootstrap-php-file] for the details. + +**Important:** After installing MCP Inspector, restart Claude Code (type `/exit` and run `claude` again) to activate the connection. + + +Testing Your Setup +================== + +Let's verify everything works. Try these prompts: + + +Test the Plugin Knowledge +------------------------- + +Type: + +``` +What's the recommended directory structure for a Nette application? +``` + +Claude should respond with detailed information about presenters, models, templates, and configuration - knowledge that comes from the `nette-architecture` skill. + + +Test MCP Inspector +------------------ + +Type: + +``` +What services do I have registered in my DI container? +``` + +If MCP Inspector is working, Claude will call `di_get_services()` and show you the actual services from your application. If you see a list of your real services, congratulations - everything is set up correctly! + + +Test Database Introspection +--------------------------- + +If your application uses a database, try: + +``` +What tables do I have? Show me the columns in the user table. +``` + + +Your First Real Task +==================== + +Now that everything is set up, let's do something useful. Try this prompt: + +``` +I need a simple ArticlePresenter with list and detail actions. +Generate the presenter, templates, and tell me what routes I need. +``` + +Watch as Claude generates a complete, working presenter following Nette conventions. It will: +- Create the presenter class with proper type hints +- Generate Latte templates for both actions +- Suggest the appropriate route configuration + +If you have MCP Inspector set up and an `article` table in your database, try: + +``` +Look at my article table and generate an ArticleRow entity with proper type hints. +``` + + +Other AI Tools +============== + +While we recommend Claude Code for the best Nette experience, MCP Inspector works with any MCP-compatible tool. + + +Cursor +------ + +Cursor is a popular AI-first code editor. To use MCP Inspector with Cursor: + +1. Install MCP Inspector as described in [Setting Up MCP Inspector |#setting-up-mcp-inspector]; when your project already has a `.cursor` directory, `init` writes `.cursor/mcp.json` for you +2. Otherwise create `.cursor/mcp.json` in your project: + +```json +{ + "mcpServers": { + "nette-inspector": { + "command": "php", + "args": ["vendor/bin/mcp-inspector"] + } + } +} +``` + +3. Restart Cursor + +Note: Cursor doesn't have the Nette-specific skills that the Claude Code plugin provides, but MCP Inspector will still give it access to your application's services, database, routes, and logs. + + +VS Code +------- + +VS Code with GitHub Copilot supports MCP servers natively. When your project already has a `.vscode` directory, `init` writes `.vscode/mcp.json` for you; otherwise create it with a `servers` entry as described in [Other AI Tools |mcp-inspector#other-ai-tools]. + + +Other MCP Tools +--------------- + +Any tool supporting the Model Context Protocol can use MCP Inspector. See the [MCP Inspector page |mcp-inspector#other-ai-tools] for setup instructions. + + +What's Next +=========== + +You're now ready for AI-assisted Nette development! Here's where to go from here: + +- [MCP Inspector |mcp-inspector] - Learn about every introspection tool +- [Claude Code Plugin |agent-plugins] - Explore all the skills (Claude Code users) +- [Vibe Coding in Practice |vibe-coding] - Get the most out of your AI assistant + +{{description: Set up vibe coding with Nette in about ten minutes: install Claude Code, add the Nette plugin, and connect MCP Inspector to your application.}} diff --git a/ai/en/guide.texy b/ai/en/guide.texy new file mode 100644 index 0000000000..3ec981a90e --- /dev/null +++ b/ai/en/guide.texy @@ -0,0 +1 @@ +{{redirect: @home}} diff --git a/ai/en/mcp-inspector.texy b/ai/en/mcp-inspector.texy new file mode 100644 index 0000000000..997da1d359 --- /dev/null +++ b/ai/en/mcp-inspector.texy @@ -0,0 +1,325 @@ +MCP Inspector +************* + +
+ +"MCP Inspector":https://github.com/nette/mcp-inspector lets an AI assistant look directly into your Nette application: it sees which services are registered in the DI container, what your database tables look like, which route leads where and what Tracy logged last night. You will learn: + +- how the inspector works and what it can see +- how to install it in two commands +- what each tool does and how to try it from the terminal +- how to keep the AI on a short leash: read-only queries, masked secrets, a kill switch + +
+ +Without the inspector, the AI guesses your application from patterns it picked up during training. With it, the AI asks your application and gets the truth: the real columns, the real service names, the real error. + +.[caution] +MCP Inspector is still in **early development and has no stable release yet**. Until the first release, install it with `composer require --dev nette/mcp-inspector:@dev` and expect tool names and configuration to keep changing. + + +How It Works +============ + +MCP Inspector is a server speaking the **Model Context Protocol (MCP)**, the standard through which AI tools such as Claude Code, Cursor or VS Code call external tools. Your editor starts the inspector as a background process, and whenever the AI needs something from your application, it calls one of the inspector's tools and gets the answer back. + +To answer, the inspector builds your application's DI container. It does that through a small script `mcp-bootstrap.php` in your project root, which returns your application's `Configurator` with all configs added; the inspector creates the container itself, in debug mode, in its own temp directory, so it never touches your web's cache. + +The container stays alive between calls, but every call checks whether your configuration changed. When you edit `services.neon`, the next tool call already sees the new services; no restart of the editor is needed. If a rebuild fails, say because of a typo in your config, the inspector keeps serving the last working container and adds a `_warning` field to the result so the AI tells you about the failure right away. + +Everything is **read-only by default**: the inspector reads your services, schema, routes and logs, but it cannot change your data or configuration and it never executes code from the AI. The single exception, running modifying SQL, is switched off unless you explicitly [enable it |#database-configuration]. + + +Installation +============ + +Two commands. The first adds the package as a development dependency, the second generates the files the inspector needs: + +```shell +composer require --dev nette/mcp-inspector:@dev +vendor/bin/mcp-inspector init +``` + +`init` creates three files and never overwrites an existing one: + +| File | Purpose +|------|------ +| `mcp-bootstrap.php` | returns your application's `Nette\Bootstrap\Configurator` (see below) +| `config/mcp-inspector.neon` | the inspector's configuration: what the AI may do +| `.mcp.json` | registers the `nette-inspector` server for Claude Code; `.cursor/mcp.json` and `.vscode/mcp.json` get the same entry when those directories exist + +Then restart your AI tool (in Claude Code type `/exit` and run `claude` again): MCP servers connect when the tool starts. + +Two options come in handy. When PHP does not run directly on your machine, pass the command the AI tool should use: `--php="ddev exec php"`. When the project is not the current directory, add `--project=PATH`. + +Is it working? Ask the AI: + +``` +What services do I have registered in the DI container? +``` + +If the answer lists the real services of your application, you are done. + + +The mcp-bootstrap.php File +========================== + +The inspector needs a `Configurator` with all configs added but *before* `createContainer()` is called, because it builds the container itself. `init` looks at your `App\Bootstrap` class and generates the file accordingly: + +- **Static `App\Bootstrap::boot(): Configurator`** (the classic Web Project): the file is simply `return App\Bootstrap::boot();` +- **Object `Bootstrap` with `bootWebApplication(): Container`** (Web Project since 2024): add a method that stops before creating the container and use it from both places: + +```php +public function bootWebApplication(): Nette\DI\Container +{ + return $this->bootConfigurator()->createContainer(); +} + +public function bootConfigurator(): Configurator +{ + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator; +} +``` + +`mcp-bootstrap.php` then reads `return (new App\Bootstrap)->bootConfigurator();`. + +- **Custom bootstrap** (a constructor with arguments, multi-tenant applications and similar): `init` writes a template with a `TODO` comment for you to complete. Keep the `new Configurator` call inside your `Bootstrap` class so Nette's `%appDir%` autodetection, which looks at the file creating the Configurator, keeps working. Environment variables set in `.mcp.json` are a handy way to pass parameters: + +```php +$blog = getenv('BLOG') === 'phpfashion' ? App\Blog::PhpFashion : App\Blog::LaTrine; +return (new App\Bootstrap($blog))->bootConsoleConfigurator(); +``` + +You do not need to switch on debug mode; the inspector does that itself, because the CLI never autodetects it and debug mode is what makes the live reload of configuration possible. + + +Tools +===== + +The tools are grouped by what they look at. Each group appears only when your application has the corresponding part: without `nette/database` there are no `db_*` tools, and the AI is not confused by tools that cannot work. + + +Application +----------- + +| Tool | What it does +|------|------ +| `app_get_info` | PHP and Nette versions, installed Nette packages, directories and the database driver + +The AI is told to call this first, so the code it writes matches the versions you actually use, PHP 8.3 attributes for example, or the Nette 3.3 way of doing things. + + +DI Container +------------ + +| Tool | What it does +|------|------ +| `di_get_services` | lists services with their types, tags, aliases and autowiring, optionally filtered by a substring of the name or type +| `di_get_service` | details of one service, including whether it has already been created +| `di_find_by_type` | services implementing a class or interface, and which of them autowiring picks +| `di_find_by_tag` | services carrying a tag, with the tag values +| `di_get_parameter_names` | parameter names, nested ones in dotted notation (`database.default.dsn`) +| `di_get_parameter` | the value of one parameter; secrets (`password`, `token`, `dsn`, …) are masked + +When you ask "what mailers do I have?", the AI calls `di_find_by_type("Nette\Mail\Mailer")` and sees exactly what your container holds. Only runtime data is available: the inspector knows the type, tags and aliases of a service, not the factory expression or the `setup` calls from your config. These tools require `nette/di` 3.2.7 or newer; parameters additionally need to be exported, and if your config has `di: export: parameters: no`, the tools tell you so. + + +Router +------ + +| Tool | What it does +|------|------ +| `router_get_routes` | all registered routes with masks, defaults and module prefixes +| `router_match_url` | which presenter and action handle a URL, with the parameters (e.g. `/article/123`) +| `router_generate_url` | the URL for a presenter and action, the way `{link}` does it (e.g. `Article:show` with `{"id": 5}`) + +The inspector runs without an HTTP request, so your application cannot tell its own address the way it does on the web. Tell it in the application's configuration (`nette/http` 3.4): + +```neon +http: + baseUrl: https://example.com/ +``` + +Without it, `router_generate_url` reports an error saying what to set, and relative URLs passed to `router_match_url` are matched against `http://localhost/`. + + +Database +-------- + +| Tool | What it does +|------|------ +| `db_get_tables` | tables and views +| `db_get_columns` | columns of a table: types, nullability, defaults, primary and foreign keys +| `db_get_relationships` | foreign key relationships between all tables (belongsTo, hasMany) +| `db_get_indexes` | indexes of a table +| `db_query` | runs one SQL statement with values bound to `?` placeholders; read-only by default +| `db_explain_query` | runs `EXPLAIN` on a `SELECT` query + +This is the group that ends the guessing about your schema. "Generate an entity for the product table" becomes a call to `db_get_columns("product")` and an entity with the columns you really have. + +`db_query` lets the AI look at the data as well, for example to see what values a status column really holds. By default it accepts only `SELECT`-like statements (`SELECT`, `SHOW`, `EXPLAIN`, `DESCRIBE`, `WITH`, `VALUES`, `TABLE`, a single statement, no `INTO OUTFILE`) and runs them inside a read-only transaction on MySQL, PostgreSQL and SQLite, so the database itself rejects anything the validator would miss. Values of columns whose names suggest secrets are masked, and the number of rows is limited. + + +Tracy +----- + +| Tool | What it does +|------|------ +| `tracy_get_log` | the newest entries of a log by level (`exception` by default, `error`, `warning`, …), each with the name of its report +| `tracy_get_report` | an exception report as Tracy writes it for agents: the code around the exception, the stack trace with arguments and the environment + +Debugging changes shape with these two. Instead of copying a stack trace into the chat, you say "check the log and tell me what broke", and the AI reads the exception itself. The log directory is the one your application's Tracy logger writes to, nothing to configure. The markdown reports require Tracy 2.12 or newer; older HTML-only reports cannot be read. + + +Trying Tools From the Terminal +============================== + +You do not need an AI tool to see what a tool returns. The `call` command runs a tool exactly as the client would and prints the result as JSON: + +```shell +vendor/bin/mcp-inspector call app_get_info +vendor/bin/mcp-inspector call router_match_url '{"url": "/article/123"}' +vendor/bin/mcp-inspector call db_query '{"query": "SELECT * FROM product WHERE id = ?", "params": [1]}' +``` + +This is the fastest way to check your bootstrap and configuration, and to see what the AI will see. The options `--project`, `--bootstrap` and `--config` work here too. + + +Configuration +============= + +The inspector is itself a small Nette application, and `config/mcp-inspector.neon` is the configuration of its own DI container, with the usual `parameters:`, `services:` and one section per group of tools. Every section is optional; a missing file means the defaults. This is the file `init` generates: + +```neon +# Configuration of nette/mcp-inspector: a Nette DI config for the inspector's own container. +# The inspector reads it itself, do not add it to the application's configs. +# Every section is optional; missing keys use the defaults shown here. + +inspector: + # false keeps the inspector from starting at all + enabled: true + # tool names or patterns hidden from the agent, e.g. [db_*, tracy_get_log] + disableTools: [] + +database: + # true: only SELECT-like statements, run in a read-only transaction + # false: any statement, the agent can modify data + readOnly: true + # maximum number of rows returned by db_query + rowLimit: 100 +``` + +The inspector reads this file itself; do not add it to your application's configs. Changes take effect after the MCP server restarts, which the AI tool does together with its session. + + +Hiding Tools +------------ + +`disableTools` takes tool names or patterns with `*`. Do you not want the AI to read your data at all? Hide the whole database group: + +```neon +inspector: + disableTools: [db_*] +``` + + +Database Configuration +---------------------- + +The only real security question is whether the AI may modify data, and by default it may not. When you do want it to, say on a throwaway development database, switch the protection off: + +```neon +database: + readOnly: false + rowLimit: 500 +``` + +With `readOnly: false` any statement runs, `UPDATE`, `DELETE` and DDL included. AI tools such as Claude Code then ask you for confirmation before each `db_query`, because the tool no longer declares itself read-only. + + +Other AI Tools +============== + +MCP Inspector works with any tool that speaks MCP. `init` registers it for Claude Code in `.mcp.json`, and for Cursor and VS Code when it finds their `.cursor` or `.vscode` directories in your project. For any other tool, register a command that starts the server over standard input and output: + +```json +{ + "mcpServers": { + "nette-inspector": { + "type": "stdio", + "command": "php", + "args": ["vendor/bin/mcp-inspector"] + } + } +} +``` + +The command runs in your project root. Where it does not (some editors start servers elsewhere), add `"--project=/path/to/project"` to the arguments. Consult your AI tool's documentation for where the configuration file lives. + + +Security +======== + +The inspector exposes the DI graph, configuration and data of whatever application it is pointed at, so point it at development environments and development data only. There is no reliable way for a CLI process to tell that it runs on a production server, so the protection is layered instead: + +1. **Development dependency**: install it with `--dev`, and `composer install --no-dev` on the server never installs it. +2. **Safe defaults**: nothing modifies data, secrets are masked, no tool executes PHP code or writes files. +3. **A kill switch**: `inspector: enabled: false` in `config/mcp-inspector.neon` or the `MCP_INSPECTOR_DISABLED=1` environment variable makes the server refuse to start. + +Two more things happen quietly. Values under keys that look like secrets (`password`, `secret`, `token`, `apiKey`, `dsn`, …) come out as `***`, both in parameters and in query results. And results carrying data from your application, database rows and log entries, are marked as untrusted, so the AI knows not to follow instructions it might find in them; a user comment saying "ignore your previous instructions" stays a comment. + + +Custom Toolkits +=============== + +Your application has facts of its own that the AI would like to know: the pending orders, the feature flags, the tenants. Add a toolkit: a class implementing `Nette\McpInspector\Toolkit` whose public methods marked with `#[McpTool]` become tools. The docblock is the tool's description, so write it for the AI: what the tool returns and when to call it. + +```php +namespace App\Mcp; + +use Mcp\Capability\Attribute\McpTool; +use Mcp\Schema\ToolAnnotations; +use Nette\McpInspector\AppContainer; +use Nette\McpInspector\Toolkit; +use Nette\McpInspector\UntrustedData; + +class BlogToolkit implements Toolkit +{ + public function __construct( + private AppContainer $app, + ) {} + + public function isAvailable(): bool + { + return true; + } + + /** + * Get a blog post by ID. + * @param int $id Post ID + */ + #[UntrustedData] + #[McpTool(name: 'blog_get_post', title: 'Blog post', annotations: new ToolAnnotations(readOnlyHint: true))] + public function getPost(int $id): array + { + $post = $this->app->get()->getByType(BlogFacade::class)->getPost($id); + return $post ? ['id' => $post->id, 'title' => $post->title] : ['error' => 'not found']; + } +} +``` + +A few things to notice. The toolkit depends on `AppContainer`, whose `get()` returns the current container of your application, so config reloads are honoured; through it you reach any service. `isAvailable()` lets a toolkit step aside when the application lacks what it needs. The `#[UntrustedData]` attribute marks a tool whose result carries data from the application (posts, comments, user input), and the inspector then tells the AI not to follow instructions found in it. And `readOnlyHint: true` tells the AI tool that the call is safe to run without asking you each time. + +Register the toolkit as a service in the inspector's configuration, not in your application's: + +```neon +# config/mcp-inspector.neon +services: + - App\Mcp\BlogToolkit +``` + +The AI can now call `blog_get_post` like any built-in tool. Try it first from the terminal: `vendor/bin/mcp-inspector call blog_get_post '{"id": 1}'`. + +{{composer: nette/mcp-inspector}} diff --git a/ai/en/tips.texy b/ai/en/tips.texy new file mode 100644 index 0000000000..d348348e9a --- /dev/null +++ b/ai/en/tips.texy @@ -0,0 +1 @@ +{{redirect: vibe-coding}} diff --git a/ai/en/vibe-coding.texy b/ai/en/vibe-coding.texy new file mode 100644 index 0000000000..f7d07eadd1 --- /dev/null +++ b/ai/en/vibe-coding.texy @@ -0,0 +1,298 @@ +Vibe Coding in Practice +*********************** + +
+ +Vibe coding means describing what you want in plain language and letting the agent write the PHP. It is a powerful way to work, but like any tool you get better results when you know how to use it. This page collects practical advice from real-world experience with Nette. + +- How to write prompts that get better results +- Making the most of MCP Inspector +- Proven workflows for common tasks +- Mistakes to avoid + +
+ + +Writing Better Prompts +====================== + + +The Art of Being Specific +------------------------- + +The single biggest improvement you can make is being specific. Compare these two prompts: + +**Vague prompt:** + +``` +Create a form +``` + +This gives the AI almost no context. What form? What fields? What validation? The AI has to make assumptions, and those assumptions might not match what you need. + +**Specific prompt:** + +``` +Create a ProductForm with: +- name: text field, required, max 100 characters +- price: float field, required, must be positive +- description: textarea, optional +- category: select from CategoryRow entities + +Use Bootstrap 5 rendering. The form should work for both creating new products and editing existing ones. +``` + +Now the AI knows exactly what you need and can generate code that works on the first try. + + +Point to Existing Patterns +-------------------------- + +Your codebase already has patterns. Instead of explaining them, point the AI to examples: + +``` +Create an OrderPresenter. Follow the same patterns as ProductPresenter – +same structure, same way of handling forms, same template organization. +``` + +The AI will read ProductPresenter and replicate the patterns you're already using. + + +Let MCP Do the Heavy Lifting +---------------------------- + +If you have MCP Inspector installed (and you should!), don't explain your application - let the AI discover it: + +**Instead of:** + +``` +My product table has columns: id (int), name (varchar), price (decimal), +category_id (int, foreign key to category), created_at (datetime)... +``` + +**Just say:** + +``` +Generate an entity for the product table. +``` + +The AI will call `db_get_columns("product")` and see the actual schema. The generated entity will match your real database, including any columns you might have forgotten to mention. + + +Give Context About Your Goals +----------------------------- + +AI can't read your mind. If there's a reason behind your request, share it: + +``` +I need to optimize the product listing page. It's currently loading +all products at once, which is slow when there are thousands of items. +The page needs to support filtering by category and sorting by price or name. +``` + +This helps the AI suggest an appropriate solution (pagination, lazy loading, caching) rather than just blindly implementing what you asked for. + + +Working with MCP Inspector +========================== + +MCP Inspector is most powerful when you use it strategically. + + +Explore Before You Build +------------------------ + +Starting a new feature? Let the AI understand the context first: + +``` +I'm going to add order tracking. Before we start: +1. What services do I have related to orders? +2. What does my order table look like? +3. What routes handle order-related pages? +``` + +This gives the AI context about your existing code, so the new feature fits naturally. + + +Debug Smarter +------------- + +When something goes wrong, don't describe the error - let the AI see it: + +``` +Something broke. Check the Tracy log for the last exception +and tell me what went wrong. +``` + +The AI will call `tracy_get_log()` and `tracy_get_report()`, read the stack trace, and can often identify the problem faster than you could explain it. + + +Verify Before You Create +------------------------ + +Before adding new routes or links, verify what exists: + +``` +What presenter handles /admin/products/edit? I want to make sure +I'm not creating something that conflicts. +``` + + +Common Workflows +================ + +Here are proven approaches for common tasks. + + +Creating a Complete CRUD +------------------------ + +Don't ask for one piece at a time. Give the AI the full picture: + +``` +Create a complete CRUD for managing products: + +1. ProductPresenter with actions: list, add, edit, delete +2. ProductForm as a component (works for both add and edit) +3. Latte templates for all actions +4. Route suggestions + +Use the actual product table schema. Follow the patterns +in CategoryPresenter if it exists. +``` + + +Adding a New Feature +-------------------- + +Break it down into phases that build on each other: + +``` +I need to add customer reviews to products. + +Phase 1 - Data layer: +- Look at the product table +- Suggest the review table schema +- Create ReviewRow entity + +Phase 2 - Business logic: +- Create ReviewService for CRUD operations +- Add methods to get reviews for a product + +Phase 3 - UI: +- Add review display to ProductPresenter:detail +- Create ReviewForm for submitting reviews +``` + + +Refactoring Existing Code +------------------------- + +Let the AI understand before it changes: + +``` +Analyze the OrderService class. What does each method do? +Are there any code smells or improvements you'd suggest? +``` + +Then: + +``` +The calculateTotal method is doing too much. Split it into +smaller methods while keeping the same public interface. +``` + + +Mistakes to Avoid +================= + + +Not Reviewing Generated Code +---------------------------- + +AI generates code quickly, but that doesn't mean every line is perfect. Always review: + +- **Database queries** - Are they efficient? Do they need indexes? +- **Security** - Is input validated? Are there authorization checks? +- **Edge cases** - What happens with empty data? Null values? + +AI is very good, but it's still an assistant. You're the developer responsible for the final code. + + +Ignoring Validation Feedback +---------------------------- + +If you're using the [Claude Code plugins |agent-plugins], they validate your PHP, Latte, NEON, JSON and JavaScript files automatically. When one reports an error, the AI knows about it. Instead of manually fixing the error, just say: + +``` +Fix the error you just created. +``` + +The AI will read the validation output and correct the mistake. + + +Forgetting Service Registration +------------------------------- + +When the AI creates a new service class, it sometimes forgets to register it in the DI container. If you get "Service not found" errors, ask: + +``` +What changes do I need in services.neon for the new OrderExportService? +``` + + +Asking for Too Much at Once +--------------------------- + +While AI can handle complex tasks, sometimes it helps to break them down: + +**Too ambitious:** + +``` +Build me a complete e-commerce system with product catalog, +shopping cart, checkout, payments, order tracking, and admin panel. +``` + +**Better approach:** + +``` +Let's build an e-commerce system step by step. Start with the product +catalog – I need to list, view, and admin products. +``` + + +When AI Excels (and When It Doesn't) +==================================== + + +AI is Great For +--------------- + +- **Boilerplate code** - Presenters, forms, entities, basic templates +- **Following patterns** - "Do it like X but for Y" +- **Understanding code** - "What does this method do?" +- **Generating tests** - Given implementation, create tests +- **Refactoring** - Improving code structure while keeping behavior + + +Consider Manual Coding For +-------------------------- + +- **Complex business logic** - Domain rules that require careful thinking +- **Performance-critical code** - Algorithms that need optimization +- **Security-sensitive code** - Authentication, authorization, encryption +- **Novel solutions** - Things that don't follow existing patterns + +AI is a multiplier, not a replacement. It makes good developers faster, but it still needs a good developer guiding it. + + +Final Thoughts +============== + +The best way to learn AI-assisted development is to practice. Start with simple tasks, pay attention to what works, and gradually take on more complex projects. + +And remember: the AI is your assistant. You're still the developer. You make the decisions, you review the code, and you're responsible for the quality of the final product. + +Happy coding! + +{{description: Practical advice for vibe coding in PHP with Nette: writing prompts that work, using MCP Inspector well, proven workflows, and mistakes to avoid.}} diff --git a/ai/meta.json b/ai/meta.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/ai/meta.json @@ -0,0 +1 @@ +{} From 870b312639be5eb6da566b6eca7deb955b1cb181 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Thu, 24 Sep 2026 08:20:56 +0200 Subject: [PATCH 02/92] added ai.nette.org --- ai/en/agent-plugins.texy | 6 ++++ ai/en/privacy-policy.texy | 64 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 ai/en/privacy-policy.texy diff --git a/ai/en/agent-plugins.texy b/ai/en/agent-plugins.texy index 61b46f4427..d27ada4117 100644 --- a/ai/en/agent-plugins.texy +++ b/ai/en/agent-plugins.texy @@ -413,4 +413,10 @@ vendor/bin/mcp-inspector init Then restart Claude Code. See the [MCP Inspector documentation |mcp-inspector] for all the available tools. + +Privacy +======= + +The plugins collect no data. They run locally and have no servers, telemetry or analytics. See the [privacy policy |privacy-policy] for details. + {{composer: nette/agent-plugins}} diff --git a/ai/en/privacy-policy.texy b/ai/en/privacy-policy.texy new file mode 100644 index 0000000000..37ca5d2c12 --- /dev/null +++ b/ai/en/privacy-policy.texy @@ -0,0 +1,64 @@ +Privacy Policy for Nette Agent Plugins +************************************** + +
+ +This policy covers the "Nette plugins":https://github.com/nette/agent-plugins for Claude Code and Codex: `nette`, `nette-lint`, `nette-dev` and `php-fixer`. The short version: **the plugins collect no data about you**. They have no servers, no telemetry, no analytics and no accounts, and they never send your code or any information about you over the network. + +
+ +Last updated: September 24, 2026 + + +What the Plugins Are +==================== + +The plugins are open-source files that run entirely on your own computer: + +- **Skills** are plain text documents. Your AI tool reads them to learn Nette conventions. They contain no code that runs on their own. +- **Hooks** (in `nette-lint` and `php-fixer`) are small PHP scripts. Your AI tool starts them after it edits a file, and they check or reformat that file. + +The complete source code is public on "GitHub":https://github.com/nette/agent-plugins, so you can verify everything this page says. + + +What Data the Plugins Collect +============================= + +None. The plugins do not collect, store or transmit any personal data, source code, prompts, conversation content or usage statistics, and they do not share anything with Nette or with any third party. Since nothing is collected, nothing is retained. + + +What the Hooks Do on Your Computer +================================== + +When your AI tool edits a file, a hook does the following, all locally: + +- reads the edited file and, if present, the `.nette-claude.json` configuration from your project +- checks or fixes the file, either with the JSON parser bundled in the plugin or with a tool from your project or your system: `php -l`, `latte-lint`, `neon-lint`, DressCode or ESLint +- reports the result back to your AI tool, which is how the AI learns about a syntax error or a reformatted file + +The hooks change nothing but the edited file itself, and only when a fixer (DressCode or ESLint) reformats it. The tools they run may keep their usual local caches, such as the DressCode cache in your temporary directory. These tools belong to your project or system and behave according to your own configuration, including any extensions or plugins you have set up for them. + + +Third-Party Services +==================== + +A few things around the plugins do involve other services: + +- **Your AI tool.** Claude Code and Codex send your conversation, including skill text and hook results, to their providers. That processing is governed by the "Anthropic":https://www.anthropic.com/legal/privacy and "OpenAI":https://openai.com/policies/privacy-policy/ privacy policies, not by this one. +- **Installation and updates.** Your AI tool downloads the plugins from GitHub, which is governed by the "GitHub privacy statement":https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement. +- **Installing DressCode.** The `install-php-fixer` skill, which you have to start yourself, runs Composer to download DressCode from Packagist and GitHub. +- **ESLint.** The ESLint hook starts ESLint through `npx`. If your project does not have ESLint installed, `npx` may download it from the npm registry. This is an ordinary package download; none of your code is sent. + + +Changes to This Policy +====================== + +If the plugins ever start handling data, this page will be updated before that version is released, and the date at the top will change. The history of the page is public in the "documentation repository":https://github.com/nette/docs. + + +Contact +======= + +The plugins are maintained by David Grudl and the Nette Foundation. If you have a question about this policy or about the plugins, please open an issue at "github.com/nette/agent-plugins":https://github.com/nette/agent-plugins/issues. + +{{description: The Nette plugins for Claude Code and Codex collect no data. They run locally and have no servers, telemetry or analytics.}} From c47116c8aa0521e0fa1c2bcf1be4d3d63e0689ad Mon Sep 17 00:00:00 2001 From: David Grudl Date: Fri, 23 Jan 2026 16:21:31 +0100 Subject: [PATCH 03/92] latte 3.1.2 --- latte/cs/develop.texy | 8 ++++---- latte/en/develop.texy | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/latte/cs/develop.texy b/latte/cs/develop.texy index 4e56b9c935..bb143c6708 100644 --- a/latte/cs/develop.texy +++ b/latte/cs/develop.texy @@ -27,7 +27,7 @@ Jak vykreslit šablonu? Stačí k tomu tento jednoduchý kód: ```php $latte = new Latte\Engine; // adresář pro cache -$latte->setTempDirectory('/path/to/tempdir'); +$latte->setCacheDirectory('/path/to/tempdir'); $params = [ /* proměnné šablony */ ]; // or $params = new TemplateParameters(/* ... */); @@ -184,18 +184,18 @@ Ve striktním režimu parsování Latte kontroluje, zda nechybí uzavírací HTM ```php $latte = new Latte\Engine; -$latte->setStrictParsing(); +$latte->setFeature(Latte\Feature::StrictParsing); ``` Generování šablon s hlavičkou `declare(strict_types=1)` zapnete takto: ```php $latte = new Latte\Engine; -$latte->setStrictTypes(); +$latte->setFeature(Latte\Feature::StrictTypes); ``` .[note] -Od verze Latte 3.1 jsou strict types povoleny ve výchozím nastavení. Můžete je deaktivovat pomocí `$latte->setStrictTypes(false)`. +Od verze Latte 3.1 jsou strict types povoleny ve výchozím nastavení. Můžete je deaktivovat pomocí `$latte->setFeature(Latte\Feature::StrictTypes, false)`. Migrační varování .{data-version:3.1} diff --git a/latte/en/develop.texy b/latte/en/develop.texy index 4a3b8ac8f0..5e350fcdbd 100644 --- a/latte/en/develop.texy +++ b/latte/en/develop.texy @@ -27,7 +27,7 @@ How to render a template? Just use this simple code: ```php $latte = new Latte\Engine; // cache directory -$latte->setTempDirectory('/path/to/tempdir'); +$latte->setCacheDirectory('/path/to/tempdir'); $params = [ /* template variables */ ]; // or $params = new TemplateParameters(/* ... */); @@ -184,18 +184,18 @@ In strict parsing mode, Latte checks for missing closing HTML tags and also disa ```php $latte = new Latte\Engine; -$latte->setStrictParsing(); +$latte->setFeature(Latte\Feature::StrictParsing); ``` To generate templates with the `declare(strict_types=1)` header, do the following: ```php $latte = new Latte\Engine; -$latte->setStrictTypes(); +$latte->setFeature(Latte\Feature::StrictTypes); ``` .[note] -Since Latte 3.1, strict types are enabled by default. You can disable them with `$latte->setStrictTypes(false)`. +Since Latte 3.1, strict types are enabled by default. You can disable them with `$latte->setFeature(Latte\Feature::StrictTypes, false)`. Migration Warnings .{data-version:3.1} From a1401e7be72df3f492492869621432398ce16273 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 17 Mar 2026 21:23:48 +0100 Subject: [PATCH 04/92] latte: info about whitespace --- latte/cs/syntax.texy | 33 +++++++++++++++++++++++++++++++++ latte/en/syntax.texy | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/latte/cs/syntax.texy b/latte/cs/syntax.texy index 1e21ab6dcf..f51b804177 100644 --- a/latte/cs/syntax.texy +++ b/latte/cs/syntax.texy @@ -218,6 +218,39 @@ Uvnitř značek fungují PHP komentáře: ``` +Řízení bílých znaků +=================== + +Latte zachází s bílými znaky inteligentně. Kód můžete volně odsazovat pro čitelnost a výstup zůstane čistý. Když se tag objeví na řádku sám, celý řádek (odsazení i konec řádku) se z výstupu odstraní: + +```latte +
    + {foreach $items as $item} +
  • {$item}
  • + {/foreach} +
+``` + +Vypíše: + +```html +
    +
  • foo
  • +
  • bar
  • +
+``` + +A co když tag není na řádku sám, ale je tam i další obsah? Bílé znaky před tagem pak patří *dovnitř* tagu: + +```latte +
+ {if $foo}hello{/if} +
+``` + +Odsazení je tedy fakticky uvnitř `{if}`: pokud je `$foo` false, nevypíše se nic - ani odsazení, ani prázdný řádek. Pokud je `$foo` true, výstup přirozeně obsahuje odsazení. Prostě pište přehledně odsazené šablony a výstup bude vždy čistý. + + Syntaktický cukr ================ diff --git a/latte/en/syntax.texy b/latte/en/syntax.texy index ef926e75be..31437eb77d 100644 --- a/latte/en/syntax.texy +++ b/latte/en/syntax.texy @@ -218,6 +218,39 @@ PHP comments work inside tags: ``` +Whitespace Control +================== + +Latte handles whitespace intelligently. You can freely indent your code for readability, and the output stays clean. When a tag appears alone on a line, the entire line (indentation and newline) is removed from the output: + +```latte +
    + {foreach $items as $item} +
  • {$item}
  • + {/foreach} +
+``` + +Outputs: + +```html +
    +
  • foo
  • +
  • bar
  • +
+``` + +What if a tag isn't alone on a line, but appears alongside other content? The whitespace before the tag then belongs *inside* the tag: + +```latte +
+ {if $foo}hello{/if} +
+``` + +The indentation is effectively inside `{if}`: when `$foo` is false, nothing is output - not even the indentation or a blank line. When `$foo` is true, the output naturally includes the indentation. You simply write well-structured templates and the output is always clean. + + Syntactic Sugar =============== From 97eabb45ad0a26441f332c9d068fec48a59dda12 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 23 Dec 2025 22:05:06 +0100 Subject: [PATCH 05/92] latte 3.1.3 --- latte/cs/develop.texy | 64 +++++++++++++++++++++++++++++++++++++++++++ latte/cs/filters.texy | 58 ++++++++++++++++++++++++++++++++++++--- latte/cs/syntax.texy | 2 ++ latte/en/develop.texy | 64 +++++++++++++++++++++++++++++++++++++++++++ latte/en/filters.texy | 58 ++++++++++++++++++++++++++++++++++++--- latte/en/syntax.texy | 2 ++ 6 files changed, 240 insertions(+), 8 deletions(-) diff --git a/latte/cs/develop.texy b/latte/cs/develop.texy index bb143c6708..759c233dc8 100644 --- a/latte/cs/develop.texy +++ b/latte/cs/develop.texy @@ -216,6 +216,70 @@ Pokud je toto zapnuto, Latte kontroluje vykreslované atributy a vyvolá uživat Jakmile jsou všechna varování vyřešena, vypněte varování o migraci a **odstraňte všechny** filtry `|accept` ze svých šablon, protože již nejsou potřeba. +Scopované proměnné cyklu .{data-version:3.1.3} +============================================== + +Ve výchozím nastavení zůstávají proměnné definované v cyklu `{foreach}` (jako `$key` a `$value`) dostupné i po jeho skončení - stejně jako v samotném PHP. To může vést k nechtěnému přepsání proměnných, pokud má proměnná cyklu stejný název jako existující proměnná šablony. + +Funkce `ScopedLoopVariables` omezí platnost proměnných na tělo cyklu. Po jeho skončení se obnoví původní hodnota proměnné (pokud existovala), nebo se proměnná odstraní: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::ScopedLoopVariables); +``` + +Příklad rozdílu: + +```latte +{var $item = 'original'} +{foreach [1, 2] as $item}{$item}, {/foreach} +{$item} +``` + +Bez `ScopedLoopVariables`: vypíše `1, 2, 2` (proměnná je přepsána) +Se `ScopedLoopVariables`: vypíše `1, 2, original` (proměnná je obnovena) + +Funguje to i s destrukturováním, např. `{foreach $array as [$a, $b]}`. + +.[note] +Proměnné cyklu používající reference (`{foreach $array as &$value}`) nebo přiřazení do vlastností (`{foreach $array as $obj->prop}`) nejsou scopovány, protože by to narušilo jejich účel. + + +Automatické odsazení (Dedent) .{toc: Dedent}{data-version:3.1.3} +================================================================ + +Při používání párových značek jako `{if}`, `{foreach}` nebo `{block}` se vnořený obsah často odsazuje pro lepší čitelnost. Toto odsazení se ale ve výchozím nastavení přenáší do vygenerovaného výstupu. Funkce `Dedent` ho automaticky odstraní, takže výstup zůstane čistý bez ohledu na úroveň zanoření v šabloně: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::Dedent); +``` + +Příklad: + +```latte +{if true} + Hello + World +{/if} +``` + +Bez `Dedent` by výstup obsahoval odsazení (`\tHello\n\tWorld\n`). S `Dedent` se odsazení odstraní a výstupem je `Hello\nWorld\n`. + +Hlubší odsazení uvnitř bloku zůstává zachováno relativně k základnímu odsazení: + +```latte +{if true} + Hello + Indented +{/if} +``` + +Výstup: `Hello\n\tIndented\n`. + +Odsazení v bloku musí být konzistentní (buď tabulátory, nebo mezery). Pokud se mísí, Latte vyhodí výjimku `Inconsistent indentation`. + + Překládání v šablonách .{toc: TranslatorExtension} ================================================== diff --git a/latte/cs/filters.texy b/latte/cs/filters.texy index bcce55ae3e..2f2a702724 100644 --- a/latte/cs/filters.texy +++ b/latte/cs/filters.texy @@ -10,6 +10,8 @@ V šablonách můžeme používat funkce, které pomáhají upravit nebo přefor | `breakLines` | [Před konce řádku přidá HTML odřádkování |#breakLines] | `bytes` | [formátuje velikost v bajtech |#bytes] | `clamp` | [ohraničí hodnotu do daného rozsahu |#clamp] +| `column` | [extrahuje jeden sloupec z pole |#column] +| `commas` | [spojí pole čárkami |#commas] | `dataStream` | [konverze pro Data URI protokol |#dataStream] | `date` | [formátuje datum a čas |#date] | `explode` | [rozdělí řetězec na pole podle oddělovače |#explode] @@ -259,6 +261,50 @@ Ohraničí hodnotu do daného inkluzivního rozsahu min a max. Existuje také jako [funkce |functions#clamp]. +column(string|int|null $columnKey, string|int|null $indexKey=null) .[filter]{data-version:3.1.3} +------------------------------------------------------------------------------------------------ +Vrátí z vícerozměrného pole hodnoty jednoho sloupce `$columnKey` jako nové pole. Lze použít i na pole objektů pro získání hodnot vlastností. + +```latte +{var $users = [ + [id: 30, name: 'John', age: 30], + [id: 32, name: 'Jane', age: 25], + [id: 33, age: 35], +]} + +{$users|column: 'name'} +{* vrátí ['John', 'Jane'] *} + +{$users|column: 'name', 'id'} +{* vrátí [30 => 'John', 32 => 'Jane'] *} +``` + +Pokud předáte `null` jako klíč sloupce, přeindexuje pole podle `$indexKey`. + + +commas(?string $lastGlue=null) .[filter]{data-version:3.1.3} +------------------------------------------------------------ +Spojí prvky pole čárkou a mezerou (`', '`). Jde o pohodlnou zkratku pro běžný výpis položek v čitelné podobě. + +```latte +{var $items = ['jablka', 'pomeranče', 'banány']} +{$items|commas} +{* vypíše 'jablka, pomeranče, banány' *} +``` + +Lze zadat i vlastní oddělovač pro poslední dvojici položek: + +```latte +{$items|commas: ' a '} +{* vypíše 'jablka, pomeranče a banány' *} + +{=['PHP', 'JavaScript', 'Python']|commas: ', nebo '} +{* vypíše 'PHP, JavaScript, nebo Python' *} +``` + +Viz také [#implode]. + + dataStream(string $mimetype=detect) .[filter] --------------------------------------------- Konvertuje obsah do data URI scheme. Pomocí něj lze do HTML nebo CSS vkládat obrázky bez nutnosti linkovat externí soubory. @@ -407,6 +453,8 @@ Můžete také použít alias `join`: {=[1, 2, 3]|join} {* vypíše '123' *} ``` +Viz také [#commas], [#explode]. + indent(int $level=1, string $char="\t") .[filter] ------------------------------------------------- @@ -637,19 +685,21 @@ Pamatujte, že skutečný vzhled čísel se může lišit podle nastavení země padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Doplní řetězec do určité délky jiným řetězcem zleva. +Doplní řetězec nebo číslo do určité délky jiným řetězcem zleva. ```latte {='hello'|padLeft: 10, '123'} {* vypíše '12312hello' *} +{=123|padLeft: 5, '0'} {* vypíše '00123' *} ``` padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Doplní řetězec do určité délky jiným řetězcem zprava. +Doplní řetězec nebo číslo do určité délky jiným řetězcem zprava. ```latte {='hello'|padRight: 10, '123'} {* vypíše 'hello12312' *} +{=123|padRight: 5, '0'} {* vypíše '12300' *} ``` @@ -747,14 +797,14 @@ Viz také [#ceil], [#floor]. slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] ------------------------------------------------------------------------ -Extrahuje část pole nebo řetězce. +Extrahuje část pole, řetězce nebo iterátoru. ```latte {='hello'|slice: 1, 2} {* vypíše 'el' *} {=['a', 'b', 'c']|slice: 1, 2} {* vypíše ['b', 'c'] *} ``` -Filtr funguje jako funkce PHP `array_slice` pro pole nebo `mb_substr` pro řetězce s fallbackem na funkci `iconv_substr` v režimu UTF‑8. +Filtr funguje jako funkce PHP `array_slice` pro pole nebo `mb_substr` pro řetězce. Pro iterátory vrací generátor - prvky se čtou z původního zdroje jeden po druhém a po dosažení limitu se čtení zastaví. Celý iterátor se do paměti nenačítá. Pokud je start kladný, posloupnost začné posunutá o tento počet od začátku pole/řetezce. Pokud je záporný posloupnost začné posunutá o tolik od konce. diff --git a/latte/cs/syntax.texy b/latte/cs/syntax.texy index f51b804177..b61b94f9b3 100644 --- a/latte/cs/syntax.texy +++ b/latte/cs/syntax.texy @@ -250,6 +250,8 @@ A co když tag není na řádku sám, ale je tam i další obsah? Bílé znaky p Odsazení je tedy fakticky uvnitř `{if}`: pokud je `$foo` false, nevypíše se nic - ani odsazení, ani prázdný řádek. Pokud je `$foo` true, výstup přirozeně obsahuje odsazení. Prostě pište přehledně odsazené šablony a výstup bude vždy čistý. +Pro ještě čistší výstup lze aktivovat funkci [Dedent |develop#Dedent], která odstraní i odsazení vzniklé zanořením v párových značkách jako `{if}` nebo `{foreach}`. + Syntaktický cukr ================ diff --git a/latte/en/develop.texy b/latte/en/develop.texy index 5e350fcdbd..ef765af5cb 100644 --- a/latte/en/develop.texy +++ b/latte/en/develop.texy @@ -216,6 +216,70 @@ When enabled, Latte checks rendered attributes and triggers a user warning (`E_U Once all warnings are resolved, disable migration warnings and **remove all** `|accept` filters from your templates, as they are no longer needed. +Scoped Loop Variables .{data-version:3.1.3} +=========================================== + +By default, variables defined in a `{foreach}` loop (like `$key` and `$value`) remain accessible after the loop ends - just like in PHP itself. This can lead to unintended variable overwrites when a loop variable has the same name as an existing template variable. + +The `ScopedLoopVariables` feature limits the scope of loop variables to the loop body. After the loop ends, the original variable value is restored (if it existed before), or the variable is unset: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::ScopedLoopVariables); +``` + +Example of the difference: + +```latte +{var $item = 'original'} +{foreach [1, 2] as $item}{$item}, {/foreach} +{$item} +``` + +Without `ScopedLoopVariables`: outputs `1, 2, 2` (variable is overwritten) +With `ScopedLoopVariables`: outputs `1, 2, original` (variable is restored) + +This also works with destructuring syntax, e.g. `{foreach $array as [$a, $b]}`. + +.[note] +Loop variables using references (`{foreach $array as &$value}`) or property assignments (`{foreach $array as $obj->prop}`) are not scoped, as this would break their intended purpose. + + +Automatic Dedentation .{toc: Dedent}{data-version:3.1.3} +======================================================== + +When using paired tags like `{if}`, `{foreach}`, or `{block}`, you often indent the nested content for readability. However, this indentation is included in the generated output by default. The `Dedent` feature automatically removes it, so the output stays clean regardless of how deeply you nest your Latte tags: + +```php +$latte = new Latte\Engine; +$latte->setFeature(Latte\Feature::Dedent); +``` + +Example: + +```latte +{if true} + Hello + World +{/if} +``` + +Without `Dedent`, the output would include the indentation (`\tHello\n\tWorld\n`). With `Dedent`, the indentation is stripped and the output is `Hello\nWorld\n`. + +Deeper indentation within a block is preserved relative to the base indentation: + +```latte +{if true} + Hello + Indented +{/if} +``` + +Output: `Hello\n\tIndented\n`. + +Indentation within a block must be consistent (either tabs or spaces). If they are mixed, Latte throws an `Inconsistent indentation` exception. + + Translation in Templates .{toc: TranslatorExtension} ==================================================== diff --git a/latte/en/filters.texy b/latte/en/filters.texy index c87f8ffceb..642c120dfc 100644 --- a/latte/en/filters.texy +++ b/latte/en/filters.texy @@ -10,6 +10,8 @@ In templates, we can use functions that help modify or reformat data into its fi | `breakLines` | [Inserts HTML line breaks before all newlines |#breakLines] | `bytes` | [formats size in bytes |#bytes] | `clamp` | [clamps a value to the given range |#clamp] +| `column` | [extracts a single column from an array |#column] +| `commas` | [joins an array with commas |#commas] | `dataStream` | [Data URI protocol conversion |#dataStream] | `date` | [formats the date and time |#date] | `explode` | [splits a string into an array by a delimiter |#explode] @@ -259,6 +261,50 @@ Clamps a value to the given inclusive range of min and max. Also exists as a [function |functions#clamp]. +column(string|int|null $columnKey, string|int|null $indexKey=null) .[filter]{data-version:3.1.3} +------------------------------------------------------------------------------------------------ +Returns the values of a single column `$columnKey` from a multidimensional array as a new array. Can also be used on arrays of objects to extract property values. + +```latte +{var $users = [ + [id: 30, name: 'John', age: 30], + [id: 32, name: 'Jane', age: 25], + [id: 33, age: 35], +]} + +{$users|column: 'name'} +{* returns ['John', 'Jane'] *} + +{$users|column: 'name', 'id'} +{* returns [30 => 'John', 32 => 'Jane'] *} +``` + +If you pass `null` as the column key, it will reindex the array according to `$indexKey`. + + +commas(?string $lastGlue=null) .[filter]{data-version:3.1.3} +------------------------------------------------------------ +Joins array elements with a comma and space (`', '`). A convenient shortcut for listing items in a human-readable format. + +```latte +{var $items = ['apples', 'oranges', 'bananas']} +{$items|commas} +{* outputs 'apples, oranges, bananas' *} +``` + +You can also provide a custom separator for the last pair of items: + +```latte +{$items|commas: ' and '} +{* outputs 'apples, oranges and bananas' *} + +{=['PHP', 'JavaScript', 'Python']|commas: ', or '} +{* outputs 'PHP, JavaScript, or Python' *} +``` + +See also [#implode]. + + dataStream(string $mimetype='detect') .[filter] ----------------------------------------------- Converts content to the data URI scheme. This allows embedding images into HTML or CSS without needing to link external files. @@ -407,6 +453,8 @@ You can also use the alias `join`: {=[1, 2, 3]|join} {* outputs '123' *} ``` +See also [#commas], [#explode]. + indent(int $level=1, string $char="\t") .[filter] ------------------------------------------------- @@ -637,19 +685,21 @@ Remember that the actual appearance of numbers may vary depending on the country padLeft(int $length, string $pad=' ') .[filter] ----------------------------------------------- -Pads a string to a certain length with another string from the left. +Pads a string or number to a certain length with another string from the left. ```latte {='hello'|padLeft: 10, '123'} {* outputs '12312hello' *} +{=123|padLeft: 5, '0'} {* outputs '00123' *} ``` padRight(int $length, string $pad=' ') .[filter] ------------------------------------------------ -Pads a string to a certain length with another string from the right. +Pads a string or number to a certain length with another string from the right. ```latte {='hello'|padRight: 10, '123'} {* outputs 'hello12312' *} +{=123|padRight: 5, '0'} {* outputs '12300' *} ``` @@ -747,14 +797,14 @@ See also [#ceil], [#floor]. slice(int $start, ?int $length=null, bool $preserveKeys=false) .[filter] ------------------------------------------------------------------------ -Extracts a slice of an array or a string. +Extracts a slice of an array, string, or iterator. ```latte {='hello'|slice: 1, 2} {* outputs 'el' *} {=['a', 'b', 'c']|slice: 1, 2} {* outputs ['b', 'c'] *} ``` -The filter works like the PHP function `array_slice` for arrays or `mb_substr` for strings, with a fallback to the `iconv_substr` function in UTF‑8 mode. +The filter works like the PHP function `array_slice` for arrays or `mb_substr` for strings. For iterators, it returns a generator - elements are consumed from the source one by one and reading stops once the limit is reached. The entire iterator is never loaded into memory. If `start` is non-negative, the sequence will start at that offset from the beginning of the array/string. If `start` is negative, the sequence will start that far from the end. diff --git a/latte/en/syntax.texy b/latte/en/syntax.texy index 31437eb77d..663b04f238 100644 --- a/latte/en/syntax.texy +++ b/latte/en/syntax.texy @@ -250,6 +250,8 @@ What if a tag isn't alone on a line, but appears alongside other content? The wh The indentation is effectively inside `{if}`: when `$foo` is false, nothing is output - not even the indentation or a blank line. When `$foo` is true, the output naturally includes the indentation. You simply write well-structured templates and the output is always clean. +For even cleaner output, you can enable the [Dedent |develop#Dedent] feature, which also removes indentation caused by nesting within paired tags like `{if}` or `{foreach}`. + Syntactic Sugar =============== From 266167312b664cefba992faab9bf603ff18d53be Mon Sep 17 00:00:00 2001 From: David Grudl Date: Mon, 23 Feb 2026 02:46:24 +0100 Subject: [PATCH 06/92] typo --- application/cs/creating-links.texy | 2 +- application/cs/templates.texy | 2 +- application/en/templates.texy | 2 +- best-practices/cs/creating-editing-form.texy | 1 - .../cs/lets-create-contact-form.texy | 3 --- best-practices/en/creating-editing-form.texy | 1 - .../en/lets-create-contact-form.texy | 3 --- caching/cs/@home.texy | 4 ++-- caching/en/@home.texy | 4 ++-- database/cs/explorer.texy | 2 +- database/en/explorer.texy | 2 +- dependency-injection/cs/faq.texy | 2 +- dependency-injection/cs/global-state.texy | 2 +- dependency-injection/en/faq.texy | 2 +- dependency-injection/en/global-state.texy | 2 +- latte/cs/custom-filters.texy | 2 +- latte/cs/custom-functions.texy | 2 +- latte/cs/custom-tags.texy | 4 ++-- latte/cs/safety-first.texy | 20 +++++++++---------- latte/cs/syntax.texy | 2 +- latte/cs/tags.texy | 2 +- latte/de/custom-tags.texy | 2 +- latte/de/safety-first.texy | 20 +++++++++---------- latte/en/custom-filters.texy | 2 +- latte/en/custom-functions.texy | 2 +- latte/en/custom-tags.texy | 4 ++-- latte/en/safety-first.texy | 20 +++++++++---------- latte/en/syntax.texy | 2 +- latte/en/tags.texy | 2 +- latte/es/custom-tags.texy | 2 +- latte/es/safety-first.texy | 20 +++++++++---------- latte/fr/custom-tags.texy | 2 +- latte/fr/safety-first.texy | 20 +++++++++---------- latte/it/custom-tags.texy | 2 +- latte/it/safety-first.texy | 20 +++++++++---------- latte/ja/custom-tags.texy | 2 +- latte/ja/safety-first.texy | 20 +++++++++---------- latte/pl/custom-tags.texy | 2 +- latte/pl/safety-first.texy | 20 +++++++++---------- latte/ru/custom-tags.texy | 2 +- latte/ru/safety-first.texy | 20 +++++++++---------- latte/tr/custom-tags.texy | 2 +- latte/tr/safety-first.texy | 20 +++++++++---------- quickstart/cs/@home.texy | 3 +-- quickstart/cs/comments.texy | 2 +- quickstart/en/@home.texy | 3 +-- quickstart/en/comments.texy | 2 +- security/cs/authentication.texy | 2 +- security/en/authentication.texy | 2 +- tracy/cs/guide.texy | 2 +- tracy/cs/stopwatch.texy | 2 +- tracy/en/stopwatch.texy | 2 +- utils/cs/filesystem.texy | 2 +- utils/en/filesystem.texy | 2 +- 54 files changed, 144 insertions(+), 154 deletions(-) diff --git a/application/cs/creating-links.texy b/application/cs/creating-links.texy index 011beea750..8658cb3646 100644 --- a/application/cs/creating-links.texy +++ b/application/cs/creating-links.texy @@ -12,7 +12,7 @@ Tvořit odkazy v Nette je jednoduché, jako ukazovat prstem. Stačí jen namíř -Díky [obousměrnému routování |routing] nebudete nikdy muset do šablon či kódu zapisovat navrdo URL adresy vaší aplikace, které se mohou později měnit, nebo je komplikovaně skládat. V odkazu stačí uvést presenter a akci, předat případné parametry a framework už URL vygeneruje sám. Vlastně je to velice podobné, jako když voláte funkci. To se vám bude líbit. +Díky [obousměrnému routování |routing] nebudete nikdy muset do šablon či kódu zapisovat natvrdo URL adresy vaší aplikace, které se mohou později měnit, nebo je komplikovaně skládat. V odkazu stačí uvést presenter a akci, předat případné parametry a framework už URL vygeneruje sám. Vlastně je to velice podobné, jako když voláte funkci. To se vám bude líbit. V šabloně presenteru diff --git a/application/cs/templates.texy b/application/cs/templates.texy index 568b40837e..5e4452a586 100644 --- a/application/cs/templates.texy +++ b/application/cs/templates.texy @@ -274,7 +274,7 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template #[Latte\Attributes\TemplateFunction] public function isWeekend(DateTimeInterface $date): bool { - return $date->format('N') >= 6 + return $date->format('N') >= 6; } } ``` diff --git a/application/en/templates.texy b/application/en/templates.texy index 31617d0259..de0585941b 100644 --- a/application/en/templates.texy +++ b/application/en/templates.texy @@ -274,7 +274,7 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template #[Latte\Attributes\TemplateFunction] public function isWeekend(DateTimeInterface $date): bool { - return $date->format('N') >= 6 + return $date->format('N') >= 6; } } ``` diff --git a/best-practices/cs/creating-editing-form.texy b/best-practices/cs/creating-editing-form.texy index 4530f060d4..d43c67faf8 100644 --- a/best-practices/cs/creating-editing-form.texy +++ b/best-practices/cs/creating-editing-form.texy @@ -130,7 +130,6 @@ Záznam si uložíme do property `$record`, abychom jej měli k dispozici v meto $this->facade->update($id, $data); // ... } -} ``` Nicméně, a to by mělo být **nejdůležitejším poznatkem celého kódu**, musíme se při tvorbě formuláře ujistit, že akce je skutečně `edit`. Protože jinak by ověření v metodě `actionEdit()` vůbec neproběhlo! diff --git a/best-practices/cs/lets-create-contact-form.texy b/best-practices/cs/lets-create-contact-form.texy index e5d44812fa..23fde0f0c5 100644 --- a/best-practices/cs/lets-create-contact-form.texy +++ b/best-practices/cs/lets-create-contact-form.texy @@ -48,9 +48,6 @@ Komponentu `contactForm` necháme vykreslit v šabloně `Home/default.latte`: Pro samotné odeslání emailu vytvoříme novou třídu, kterou nazveme `ContactFacade` a umístíme ji do souboru `app/Model/ContactFacade.php`: ```php -facade->update($id, $data); // ... } -} ``` However, and this should be **the most important takeaway from the entire code**, we must ensure the action is indeed `edit` when creating the form. Otherwise, the verification in the `actionEdit()` method would not occur at all! diff --git a/best-practices/en/lets-create-contact-form.texy b/best-practices/en/lets-create-contact-form.texy index 0fd677048d..695985f569 100644 --- a/best-practices/en/lets-create-contact-form.texy +++ b/best-practices/en/lets-create-contact-form.texy @@ -48,9 +48,6 @@ Let's render the `contactForm` component in the `Home/default.latte` template: For sending the email itself, we'll create a new class named `ContactFacade` and place it in the file `app/Model/ContactFacade.php`: ```php -load($key, function () { - return ...; + return /* ... */; }, [Cache::Expire => '20 minutes']); ``` @@ -293,7 +293,7 @@ Velmi elegantně lze zachytávat a cachovat výstup: ```php if ($capture = $cache->capture($key)) { - echo ... // vypisujeme data + // echo ... vypisujeme data $capture->end(); // uložíme výstup do cache } diff --git a/caching/en/@home.texy b/caching/en/@home.texy index 5525b1d800..da8d332d6c 100644 --- a/caching/en/@home.texy +++ b/caching/en/@home.texy @@ -125,7 +125,7 @@ Or by using the 3rd parameter of the `load()` method itself, e.g.: ```php $value = $cache->load($key, function () { - return ...; + return /* ... */; }, [Cache::Expire => '20 minutes']); ``` @@ -293,7 +293,7 @@ Output can be captured and cached very elegantly: ```php if ($capture = $cache->capture($key)) { - echo ... // printing some data + // echo ... printing some data $capture->end(); // save the output to the cache } diff --git a/database/cs/explorer.texy b/database/cs/explorer.texy index ebeeee8608..d1a0d6869f 100644 --- a/database/cs/explorer.texy +++ b/database/cs/explorer.texy @@ -263,7 +263,7 @@ Usnadňuje stránkování výsledků. Přijímá číslo stránky (počítané o ```php $numOfPages = null; -$table->page(page: 3, itemsPerPage: 10, $numOfPages); +$table->page(page: 3, itemsPerPage: 10, numOfPages: $numOfPages); echo "Celkem stránek: $numOfPages"; ``` diff --git a/database/en/explorer.texy b/database/en/explorer.texy index 7e66ee3244..f1c2d6c228 100644 --- a/database/en/explorer.texy +++ b/database/en/explorer.texy @@ -263,7 +263,7 @@ Facilitates pagination of results. It accepts the page number (starting from 1) ```php $numOfPages = null; -$table->page(page: 3, itemsPerPage: 10, $numOfPages); +$table->page(page: 3, itemsPerPage: 10, numOfPages: $numOfPages); echo "Total pages: $numOfPages"; ``` diff --git a/dependency-injection/cs/faq.texy b/dependency-injection/cs/faq.texy index e9842c0bba..a64b806cbe 100644 --- a/dependency-injection/cs/faq.texy +++ b/dependency-injection/cs/faq.texy @@ -88,7 +88,7 @@ Mějme na paměti [Pravidlo č. 1: nech si to předat |introduction#Pravidlo č. V této ukázce je `%myParameter%` zástupný symbol pro hodnotu parametru `myParameter`, který se předá do konstruktoru třídy `MyClass`: -```php +```neon # config.neon parameters: myParameter: Some value diff --git a/dependency-injection/cs/global-state.texy b/dependency-injection/cs/global-state.texy index d152d69973..2f7ddf5caf 100644 --- a/dependency-injection/cs/global-state.texy +++ b/dependency-injection/cs/global-state.texy @@ -93,7 +93,7 @@ Musíte podrobně procházet kód, abyste zjistili, že objekt `PaymentGateway` ```php $db = new DB('mysql:', 'user', 'password'); -$gateway = new PaymentGateway($db, ...); +$gateway = new PaymentGateway($db, /* ... */); ``` Podobný problém se objevuje i při použití globálního přístupu k databázovému spojení: diff --git a/dependency-injection/en/faq.texy b/dependency-injection/en/faq.texy index b7f65321ba..6f8b836dc9 100644 --- a/dependency-injection/en/faq.texy +++ b/dependency-injection/en/faq.texy @@ -88,7 +88,7 @@ Keep in mind [Rule #1: Let It Be Passed to You |introduction#Rule #1: Let It Be In this example, `%myParameter%` is a placeholder for the value of the `myParameter` parameter, which will be passed to the `MyClass` constructor: -```php +```neon # config.neon parameters: myParameter: Some value diff --git a/dependency-injection/en/global-state.texy b/dependency-injection/en/global-state.texy index f626392831..794f2814b3 100644 --- a/dependency-injection/en/global-state.texy +++ b/dependency-injection/en/global-state.texy @@ -93,7 +93,7 @@ You must meticulously trace the code to discover that the `PaymentGateway` objec ```php $db = new DB('mysql:', 'user', 'password'); -$gateway = new PaymentGateway($db, ...); +$gateway = new PaymentGateway($db, /* ... */); ``` A similar problem arises when using global access to a database connection: diff --git a/latte/cs/custom-filters.texy b/latte/cs/custom-filters.texy index 7f7cab0c0c..75aff4b35e 100644 --- a/latte/cs/custom-filters.texy +++ b/latte/cs/custom-filters.texy @@ -111,7 +111,7 @@ class MyLatteExtension extends Extension // Registrace $latte = new Latte\Engine; -$latte->addExtension(new App\Latte\MyLatteExtension); +$latte->addExtension(new MyLatteExtension); ``` Tento přístup udrží logiku vašeho filtru zapouzdřenou a registraci jednoduchou. diff --git a/latte/cs/custom-functions.texy b/latte/cs/custom-functions.texy index 6c95e5287c..5dbcd45c57 100644 --- a/latte/cs/custom-functions.texy +++ b/latte/cs/custom-functions.texy @@ -95,7 +95,7 @@ class MyLatteExtension extends Extension } // Registrace (předpokládáme, že $container obsahuje DIC) -$extension = $container->getByType(App\Latte\MyLatteExtension::class); +$extension = $container->getByType(MyLatteExtension::class); $latte = new Latte\Engine; $latte->addExtension($extension); ``` diff --git a/latte/cs/custom-tags.texy b/latte/cs/custom-tags.texy index 6107840631..f9475bf575 100644 --- a/latte/cs/custom-tags.texy +++ b/latte/cs/custom-tags.texy @@ -489,7 +489,7 @@ class MyLatteExtension extends Extension // Při registraci rozšíření: $isDev = true; // Určete toto na základě prostředí vaší aplikace -$latte->addExtension(new App\Latte\MyLatteExtension($isDev)); +$latte->addExtension(new MyLatteExtension($isDev)); ``` A jeho použití v šabloně: @@ -923,7 +923,7 @@ Nyní můžete použít `n:confirm` na odkazech, tlačítkách nebo prvcích for Vygenerované HTML: -```html +```latte Smazat ``` diff --git a/latte/cs/safety-first.texy b/latte/cs/safety-first.texy index 4329f8db23..ec1ed260c6 100644 --- a/latte/cs/safety-first.texy +++ b/latte/cs/safety-first.texy @@ -33,7 +33,7 @@ echo '

Výsledky vyhledávání pro ' . $search . '

'; Útočník může do vyhledávacího políčka a potažmo do proměnné `$search` zapsat libovolný řetězec, tedy i HTML kód jako ``. Protože výstup není nijak ošetřen, stane se součástí zobrazené stránky: -```html +```latte

Výsledky vyhledávání pro

``` @@ -59,7 +59,7 @@ echo '' . $imageAlt . ''; Útočníkovi stačí jako popisek vložit šikovně sestavený řetězec `" onload="alert('Hacked!')` a když vypsání nebude ošetřeno, výsledný kód bude vypadat takto: -```html +```latte ``` @@ -91,7 +91,7 @@ Kontextově sensitivní escapování Co se přesně myslí slovem kontext? Jde o místo v dokumentu s vlastními pravidly pro ošetřování vypisovaných dat. Odvíjí se od typu dokumentu (HTML, XML, CSS, JavaScript, plain text, ...) a může se lišit v jeho konkrétních částech. Například v HTML dokumentu je takových míst (kontextů), kde platí velmi odlišná pravidla, celá řada. Možná budete překvapeni, kolik jich je. Tady máme první čtveřici: -```html +```latte

#text

@@ -108,7 +108,7 @@ Zajímavé je to uvnitř HTML komentářů. Tady se totiž k escapování nepou Kontexty se také mohou vrstvit, k čemuž dochází, když vložíme JavaScript nebo CSS do HTML. To lze udělat dvěma odlišnými způsoby, elementem a atributem: -```html +```latte @@ -132,7 +132,7 @@ Mějme řetězec `Rock'n'Roll`. Pokud jej budete vypisovat v HTML textu, zrovna v tomhle případě netřeba dělat žádné záměny, protože řetězec neobsahuje žádný znak se speciálním významem. Jiná situace nastane, pokud jej vypíšete uvnitř HTML atributu uvozeného do jednoduchých uvozovek. V takovém případě je potřeba escapovat uvozovky na HTML entity: -```html +```latte
``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); Pokud tento kód vložíme do HTML dokumentu pomocí ` ``` Pokud bychom jej však chtěli vložit do HTML atributu, musíme ještě escapovat uvozovky na HTML entity: -```html +```latte
``` @@ -170,7 +170,7 @@ https://example.org/?a=Jazz&b=Rock%27n%27Roll A když tento řetězec vypíšeme v atributu, ještě aplikujeme escapování podle tohoto kontextu a nahradíme `&` za `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Všimněte si, že okolo hodnot atributů nejsou uvozovky. Kodér na ně mohl za Útočník jako popisek obrázku vloží šikovně sestavený řetězec `foo onload=alert('Hacked!')`. Už víme, že Twig nemůže poznat, jestli se proměnná vypisuje v toku HTML textu, uvnitř atributu, HTML komentáře, atd., zkrátka nerozlišuje kontexty. A jen mechanicky převádí znaky `< > & ' "` na HTML entity. Takže výsledný kód bude vypadat takto: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Nyní se podíváme, jak si se stejnou šablonou poradí Latte: Latte vidí šablonu stejně jako vy. Na rozdíl od Twigu chápe HTML a ví, že proměnná se vypisuje jako hodnota atributu, který není v uvozovkách. Proto je doplní. Když útočník vloží stejný popisek, výsledný kód bude vypadat takto: -```html +```latte foo onload=alert('Hacked!') ``` diff --git a/latte/cs/syntax.texy b/latte/cs/syntax.texy index b61b94f9b3..11c916ee8d 100644 --- a/latte/cs/syntax.texy +++ b/latte/cs/syntax.texy @@ -233,7 +233,7 @@ Latte zachází s bílými znaky inteligentně. Kód můžete volně odsazovat p Vypíše: -```html +```latte